

,
Программирование на
языке C в
Microsoft Visual Studio 2010
2010
Курс посвящен программированию на языке С в инструментальной среде Microsoft Visual Studio 2010.
Курс состоит из теоретической и практической частей. В теоретической части приводятся основные сведения языка С, необходимые для выполнения практических заданий. Практическая часть состоит из 21 лабораторной работы, а также двух контрольных заданий, в большей мере для самостоятельного выполнения. В приложении к пособию приводятся необходимые сведения по конфигурированию создаваемого проекта в режимах Debug и Release.
0.
Предисловие - 4
1.
Знакомство со средой Microsoft Visual Studio 2010 и настройка компилятора языка С. Стандартный ввод-вывод
В лекции рассматривается инструментальная среда разработки приложений Microsoft Visual Studio 2010 в режиме компилятора языка С. Приводится простейшая программа на языке С, которая иллюстрирует использование средств элементарного текстового вывода на консоль.
2.
Переменные и базовые типы данных языка С - 29
В лекции ставится задача изучение базовых типов и их размеров языка программирования С, объявления и программы с переменными, включая простейшие арифметические операции. В практической части рассматриваются примеры с их полной программной реализацией.
3.
Организация циклов в языке С - 43
В лекции рассматриваются операторы цикла while, for, do–while. Приводятся составные операторы цикла и операторы отношения, для которых приводятся примеры с полной программной реализацией.
4.
Принятие решений. Условные операторы в языке С - 56
В лекции рассматриваются операторы if, if–else, if–else if–else, switch–case–default, оператор условия?, операторы перехода break, continue, безусловный оператор перехода goto. Изучаются вложенные условные операторы, а также логические условия.
5.
Числовые массивы в языке программирования С - 74
В лекции надлежит изучить определение и инициализацию числовых массивов в языке программирования С. Освоить программные решения типовых примеров с многомерными числовыми массивами.
6.
Символьные массивы в языке С. Работа со строками - 90
В лекции надлежит изучить задание и инициализацию символьных массивов в языке программирования С, изучить решение задач с символьными массивами, изучить базовые функции для работы со строками.
7.
Указатели в языке программирования С - 106
В лекции следует изучить указатель как средство доступа к данным. Научиться определять адреса переменных основных типов. Изучить допустимые операции с указателями. Научиться использовать указатели в элементарных задачах программирования.
8.
Указатели и массивы в языке С - 118
В лекции рассматриваются вопросы взаимосвязи указателей и массивов, как числовых, так и символьных. Рассматриваются допустимые операции с указателями и массивами, массивы указателей и указатели на указатели.
9.
Динамическое распределение памяти в языке С - 135
В лекции рассматриваются вопросы динамического распределения памяти, Изучаются функции динамического распределения памяти и их применение для числовых и символьных массивов, для хранения данных.
10.
Общие сведения о функциях языка С - 146
В лекции рассматриваются особенности объявления и определений функций, способов задания формальных параметров и типов возвращаемых данных, вызов функций, передача аргументов по значению и по ссылке.
11.
Указатели и функции в языке программирования С - 179
В лекции изучаются вопросы программирования функций, аргументами которых могут быть указатели, а также функции, возвращающие значения через указатели. В практической части рассматриваются примеры с их полной программной реализацией.
12.
Файловый ввод/вывод в языке С - 200
В лекции предполагается изучить базовые функции файловой системы языка программирования С. Научиться создавать, читать, записывать и модифицировать файлы.
13.
Структуры – производные типы данных языка С - 206
В лекции рассматриваются вопросы создания и использования структур в языке программирования С.
14.
Объединения и перечислимые типы в языке С - 214
В лекции рассматриваются вопросы создания и использования объединений и перечислимых типов в языке программирования С.
15.
Структуры и функции языка С - 223
В лекции рассматриваются способы передачи структур в функции, научиться создавать функции, которые возвращают структуры и указатели на структуры.
16.
Операции с разрядами (битами) в языке С - 238
В лекции рассматриваются операторы и операции по управлению отдельными разрядами (битами) переменных, а также операции с битовыми полями.
17.
Программы на языке С, состоящие из нескольких файлов - 260
В лекции рассматриваются вопросы сборки программы, состоящей из нескольких функций, расположенных в разных файлах, а также дополнительные обращения к функциям.
18.
Рекурсивные алгоритмы и функции - 281
В лекции ставится задача изучить различные виды рекурсии и применения рекурсивных алгоритмов, получить навыки программирования на языке С с использованием рекурсивных функций.
19.
Препроцессор языка С - 303
В лекции рассматриваются практически важные свойства препроцессора языка С и примеры типовых препроцессорных директив и конструкций.
20.
Программы на языке С при использовании статически подключаемой библиотеки - 309
В лекции ставится задача научиться создавать и применять статическую подключаемую библиотеку с помощью MS Visual Studio 2010, осуществлять компиляцию нескольких файлов, размещенных в статической библиотеке.
21.
Использование аргументов командной строки в С - 343
В лекции необходимо изучить способы передачи аргументов командной строки операционной системы Windows в программу, в которой предусмотрено считывание количества аргументов и вывод имен этих аргументов с возможностью запуска приложений (аргументов).
Программирование на языке C в Microsoft Visual Studio 2010 |
|
|
|
0. Лекция: Предисловие: |
|
|
|
Данное учебное пособие освещает практические приемы программирования на языке С (читается "Си") в среде программирования Microsoft Visual Studio 2010, которая устанавливается в режиме программирования С. Изначально язык С предназначался для системного программирования при создании операционных систем, системных утилит и встраиваемого программного обеспечения. Он обладает всеми необходимыми для этого свойствами: программы, написанные на нем, очень эффективны, не требуют специальной среды поддержки времени выполнения. Программы на языке С имеют низкие требования к аппаратной части вычислительной системы. Тем не менее в настоящее время язык С часто выбирается из-за стабильности языка и его окружения (стандартные библиотеки, компиляторы и другие инструментальные средства), а также наличия возможности получения программ, выполняющихся с максимальной скоростью на данной аппаратной платформе. Более того, язык С можно использовать и для создания веб-сайтов через технологию CGI (Common Gateway Interface – общий шлюзовый интерфейс). Немаловажно также то, что компиляторы, библиотеки и инструменты разработки на языке С существуют практически для всех систем. Программы на языке С отличаются переносимостью между платформами на уровне исходного кода. Язык C оказал большое влияние на индустрию разработки программного обеспечения. С одной стороны, синтаксис многих его инструкций лежит в основе таких языков, как С++, С#, Java, PHP. С другой – он используется в качестве промежуточного в некоторых системах программирования, когда программа сначала транслируется в программу на языке С, и только потом компилируется компилятором языка С для получения окончательного исполняемого модуля. Язык С называют компьютерным языком "среднего уровня". Но это не означает, что он менее совершенен по сравнению с традиционными языками высокого уровня, такими как Fortran, Pascal, Basic и др. Язык С сочетает элементы языков высокого уровня с функциональностью ассемблера. В нем заложены возможности для разработки конструкций, характерных для языков высокого уровня. В то же время С позволяет манипулировать битами, байтами и адресами, т. е. базовыми элементами, с которыми работает компьютер. К неоспоримым достоинствам языка С относятся следующие:
Строительными блоками языка С являются функции, с помощью которых возможно выполнение операций как высокого, так и сравнительно низкого уровня. Важным аспектом языка С является его структурированность. Специфическая черта структурированного языка – использование блоков. Блок – это набор инструкций, которые логически связаны между собой. Другая характерная особенность языка С – отсутствие ответственности за действия программиста. Например, в нем не предусматривается контроль выхода за границы массивов (числовых или символьных). Основной принцип данного языка состоит в том, чтобы позволить программисту делать все, что он хочет, но и за последствия отвечает не язык, а программист. Данное учебное пособие предназначено для начального изучения языка С в течение одного-двух семестров из расчета 51 – 68 академических часов по программе курса "Программирование на языке высокого уровня". Пособие состоит из 21 темы, каждая из которых содержит теоретическую и практическую части. В теоретической даются основные конструкции языка программирования С, которым посвящена та или иная тема. В практической части приведены примеры, задания, представлены их решения, показаны программные коды и результаты выполнения. После изучения программного кода примера предлагаются задания для самостоятельного решения (программирования). После изучения каждой темы необходимо ответить на контрольные вопросы. В завершение приводятся два примера контрольной работы и приложение с описанием типов разрабатываемых проектов. Большинство тем пособия рассчитаны на два академических часа, на изучение некоторых требуется четыре. Библиографический список после каждой темы позволит желающим самостоятельно дополнить знания по языку программирования С. |
|
Программирование на языке C в Microsoft Visual Studio 2010 |
|
|
|
1. Лекция: Знакомство со средой Microsoft Visual Studio 2010 и настройка компилятора языка С. Стандартный ввод-вывод: версия для печати и PDA |
|
|
|
Теоретическая частьЯзык С (читается как Си) в основе своей был создан в 1972 г. как язык для операционной системы UNIX [1.2]. Автором этого языка считается Ритчи (DENNIS M. RITCHIE). Популярность языка С обусловлена, прежде всего тем, что большинство операционных систем были написаны на языке С. Его начальное распространение было задержано из-за того, что не было удачных компиляторов. Несколько лет не было единой политики в стандартизации языка С. В начале 1980-х г. в Американском национальном институте стандартов (ANSI) началась работа по стандартизации языка С. В 1989 г. работа комитета по языку С была ратифицирована, и в 1990 г. был издан первый официальный документ по стандарту языка С. Появился стандарт 1989, т. е. С89 [1.3]. К разработке стандарта по языку С была также привлечена Международная организация по стандартизации (ISO). Появился стандарт ISO/IEC 9899:1990, или ANSI C99 языка С [1.3]. В данном пособии за основу принимается стандарт языка С от 1989 г. и написание программ будет выполняться в среде разработки Visual Studio 2010. Язык С является прежде всего языком высокого уровня, но в нем заложены возможности, которые позволяют программисту (пользователю) работать непосредственно с аппаратными средствами компьютера и общаться с ним на достаточно низком уровне [1.3]. Многие операции, выполняемые на языке С, сродни языку Ассемблера. Поэтому язык С часто называют языком среднего уровня. Для написания программ в практических разделах данного учебного пособия будет использоваться компилятор языка С++, а программирование будет вестись в среде Microsoft Visual Studio 2010. Предполагается, что на компьютере установлена эта интегрированная среда. Microsoft Visual Studio 2010 доступна в следующих вариантах:
Отличительной особенностью среды Microsoft Visual Studio 2010 является то, что она поддерживает работу с несколькими языками программирования и программными платформами. Поэтому, перед тем, как начать создание программы на языке С, необходимо выполнить несколько подготовительных шагов по созданию проекта и выбора и настройки компилятора языка С для трансляции исходного кода После запуска Microsoft Visual Studio 2010 появляется следующая стартовая страница, которая показана на рис. 1.1.
Следующим шагом является создание нового проекта. Для этого в меню File необходимо выбрать New – Project (или комбинацию клавиш Ctrl + Shift + N). Результат выбора пунктов меню для создания нового проекта показан на рис. 1.2.
Среда Visual Studio отобразит окно New Project, в котором необходимо выбрать тип создаваемого проекта. Проект (project) используется в Visual Studio для логической группировки нескольких файлов, содержащих исходный код, на одном из поддерживаемых языков программирования, а также любых вспомогательных файлов. Обычно после сборки проекта (которая включает компиляцию всех всходящих в проект файлов исходного кода) создается один исполняемый модуль. В окне New Project следует развернуть узел Visual С++, обратиться к пункту Win32 и на центральной панели выбрать Win32 Console Application. Выбор этой опции показан на рис. рис. 1.3.
Затем в поле редактора Name (где по умолчанию имеется <Enter_name>) следует ввести имя проекта, например, hello. В поле Location можно указать путь размещения проекта, или выбрать путь размещения проекта с помощью клавиши (кнопки) Browse. По умолчанию проект сохраняется в специальной папке Projects. Пример выбора имени проекта показано на рис. 1.4. Одновременно с созданием проекта Visual Studio создает решение. Решение (solution) – это способ объединения нескольких проектов для организации более удобной работы с ними. После нажатия кнопки OK откроется окно Win32 Application Wizard (мастер создания приложений для операционных систем Windows), показанное на рис. 1.5.
Выбор имени проекта может быть достаточно произвольным: допустимо использовать числовое значение, допустимо имя задавать через буквы русского алфавита. В дальнейшем будем использовать имя, набранное с помощью букв латинского алфавита и, может быть, с добавлением цифр.
На первой странице представлена информация о создаваемом проекте, на второй можно сделать первичные настройки проекта. После обращения к странице Application Settings, или после нажатия кнопки Next получим окно, показанное на рис. рис. 1.6.
В дополнительных опциях (Additional options) следует поставить галочку в поле Empty project (пустой проект) и снять (убрать) галочку в поле Precompiled header. Получим экранную форму, показанную на рис. 1.7.
Здесь и далее будут создавать проекты по приведенной схеме, т. е. проекты в консольном приложении, которые должны создаваться целиком программистом (за счет выбора Empty project). После нажатия кнопки Finish, получим экранную форму, показанную на рис. 1.8, где приведена последовательность действий добавления файла для создания исходного кода к проекту. Стандартный путь для этого: подвести курсор мыши к папке Source Files из узла hello в левой части открытого проекта приложения, выбрать Add и New Item (новый элемент).
После выбора (нажатия) New Item получим окно, показанное на рис. 1.9, где через пункт меню Code узла Visual C++ выполнено обращение к центральной части панели, в которой осуществляется выбор типа файлов. В данном случае требуется обратиться к закладке C++ File (.cpp).
Теперь в поле редактора Name (в нижней части окна) следует задать имя нового файла и указать расширение ".с". Например, main. c. Имя файла может быть достаточно произвольным, но имеется негласное соглашение, что имя файла должно отражать его назначение и логически описывать исходный код, который в нем содержится. В проекте, состоящем из нескольких файлов, имеет смысл выделить файл, содержащий главную функцию программы, с которой она начнет выполняться. В данном пособии такому файлу мы будем задавать имя main. c, где расширение .с указывает на то, что этот файл содержит исходный код на языке С, и он будет транслироваться соответствующим компилятором. Программам на языке С принято давать расширение .с. После задания имени файла в поле редактора Name, получим форму, показанную на рис. 1.10.
Затем следует нажать кнопку Add. Вид среды Visual Studio после добавления первого файла к проекту показан на рис. 1.11. Добавленный файл отображается в дереве Solution Explorer под узлом Source Files (файлы с исходным кодом), и для него автоматически открывается редактор.
На рис. 1.11 в левой панели в папке Solution Explorer отображаются файлы, включенные в проект в папках. Приведем описание. Папка Source Files предназначена для файлов с исходным кодом. В этой папке отображаются файлы с расширением .с. Папка Header Files сожержит заголовочные файлы с расширением .h. Папка Resource Files содержит файлы ресурсов, например изображения и т. д. Папка External Dependencies отображает файлы, не добавленные явно в проект, но использующиеся в файлах исходного кода, например включенные при помощи директивы #include. Обычно в папке External Dependencies присутствуют заголовочные файлы стандартной библиотеки, использующиеся в проекте. Следующий шаг состоит в настройке проекта. Для этого в меню Project главного меню следует выбрать hello Properties (или с помощью последовательного нажатия клавиш Alt+F7). Пример обращения к этому пункту меню показан на рис. 1.12.
После того как произойдет открытие окна свойств проекта, следует обратиться (с левой стороны) к Configuration Properties. Появится ниспадающий список, который показан на рис. 1.13. Выполнить обращение к узлу General, и через него в левой панели выбрать Character Set, где установить свойство Use Multi-byte Character Set. Настройка Character Set (набор символов) позволяет выбрать, какая кодировка символов – ANSI или UNICODE – будет использована при компиляции программы. Для совместимости со стандартом C89 мы выбираем Use Multi-Byte Character Set. Это позволяет использовать многие привычные функции, например, функции по выводу информации на консоль.
После сделанного выбора, показанного на рис. 1.13, следует нажать кнопку Применить. Затем следует выбрать узел С/С++ и в ниспадающем меню выбрать пункт Code Generation, через который следует обратиться в правой части панели к закладке Enable C++ Exceptions, для которой установить No (запрещение исключений С++). Результат установки выбранного свойства показан на рис. 1.14. После произведенного выбора нажать кнопку Применить.
Далее в ниспадающем меню узла С/С++ необходимо выбрать пункт Language и через него обратиться в правую часть панели, где установить следующие свойства: свойство Disable Language Extensions (дополнительные языковые расширения фирмы Microsoft) в Yes (/Za), свойство Treat wchar_t as Built-in Type (рассматривать тип wchar_t как встроенный тип) установить в No (/Zc:wchar_t–), свойство Force Conformance in For Loop Scope (соответствие стандарту определения локальных переменных в операторе цикла for) установить в Yes(/Zc:forScope), свойство Enable Run-Time Type Info (разрешить информацию о типах во время выполнения) установить в No (/GR–), свойство Open MP Support (разрешить расширение Open MP – используется при написании программ для многопроцессорных систем) установить в No(/openmp–). Результат выполнения этих действий показан на рис. 1.15.
После выполнения указанных действий следует нажать клавишу Применить. Далее в ниспадающем списке узла С/С++ следует выбрать пункт Advanced и в правой панели изменить свойство Compile As в свойство компиляции языка С, т. е. Compile as C Code (/TC). Результат установки компилятора языка С показан на рис. 1.16.
После нажатия клавиш Применить и ОК сначала откроется подготовленный проект с пустым полем редактора кода, в котором можно начать писать программы. В этом редакторе наберем программу, выводящую традиционное приветствие "Hello World". Для компиляции созданной программы можно обратиться в меню Build, или, например, набрать клавиши Ctr+F7. В случае успешной компиляции получим следующую экранную форму, показанную на рис. 1.17.
Для приведенного кода программы запуск на ее исполнение из окна редактора в Visual Studio 2010 можно нажать клавишу F5. рис. 1.18 показан результат исполнения первой программы.
Произведем разбор первой программы. Во-первых, надо отметить, что в языке С нет стандартных инструкций (операторов) для вывода сообщений на консоль (окно пользователя). В языке С предусматриваются специальные библиотечные файлы, в которых имеются функции для этих целей. В приведенной программе используется заголовочный файл с именем stdio. h (стандартный ввод–вывод), который должен быть включен в начало программы. Для вывода сообщения на консоль используется функция printf(). Для работы с консолью включен также заголовочный файл conio. h., который поддерживает функцию _getch(), которая извлекает символ из потока ввода, т. е. она предназначенная для приема сообщения о нажатии какой-либо (почти любой) клавиши на клавиатуре. С другими компиляторами, возможно, потребуется getch(), т. е. без префиксного нижнего подчеркивания. Строка программы int main (void) сообщает системе, что именем программы является main() – главная функция, и что она возвращает целое число, о чем указывает аббревиатура "int". Имя main() – это специальное имя, которое указывает, где программа должна начать выполнение [1.1]. Наличие круглых скобок после слова main() свидетельствует о том, что это имя функции. Если содержимое круглых скобок отсутствует или в них содержится служебное слово void, то это означает, что в функцию main() не передается никаких аргументов. Тело функции main() ограничено парой фигурных скобок. Все утверждения программы, заключенные в фигурные скобки, будут относиться к функции main(). В теле функции main() имеются еще три функции. Во-первых, функции printf() находятся в библиотеке компилятора языка С, и они печатают или отображают те аргументы, которые были подставлены вместо параметров. Символ "\n" составляет единый символ newline (новая строка), т. е. с помощью этого символа осуществляется перевод на новую строку. Символ "\t" осуществляет табуляцию, т. е. начало вывода результатов программы с отступом вправо. Функция без параметров _getch() извлекает символ из потока ввода (т. е. ожидает нажатия почти любой клавиши). С другими компиляторами, возможно, потребуется getch(), т. е. без префиксного нижнего подчеркивания. Последнее утверждение в первой программе return 0; указывает на то, что выполнение функции main() закончено и что в систему возвращается значение 0 (целое число). Нуль используется в соответствии с соглашением об индикации успешного завершения программы [1.3]. В завершение следует отметить, что все действия в программе завершаются символом точки с запятой. Все файлы проекта сохраняются в той папке, которая сформировалась после указания в поле Location имени проекта (hello). На рис. 1.19 показаны папки и файлы проекта Visual Studio 2010..
На рис. 1.19 файлы с полученными расширениями означают: hello. sln – файл решения для созданной программы. Он содержит информацию о том, какие проекты входят в данное решение. Обычно, эти проекты расположены в отдельных подкаталогах. Например, наш проект находится в подкаталоге hello; hello. suo – файл настроек среды Visual Studio при работе с решением, включает информацию об открытых окнах, их расположении и прочих пользовательских параметрах. hello. sdf – файл содержащий вспомогательную информацию о проекте, который используется инструментами анализа кода Visual Studio, такими как IntelliSense для отображения подсказок об именах и т. д. Файлы папки Debug показаны на рис. 1.20.
Рассмотрим файлы в соответствии с рис. 1.20. hello. exe – исполняемый файл проекта; hello. ilk – файл "incremental linker", используемый компоновщиком для ускорения процесса компоновки; hello. pdb – отладочная информация/информация об именах в исполняемых файлах, используемая отладчиком. Файлы папки hello показаны на рис. 1.21.
Характеристика содержимого папки hello: main. c – файл исходного программного кода, hello. vcxproj – файл проекта, hello. vcxproj. user – файл пользовательских настроек, связанных с проектом, hello. vcxproj. filters – файл с описанием фильтров, используемых Visual Studio Solution Explorer для организации и отображения файлов с исходным кодом. Практическая частьВ практической части выполните следующие задания на основе рассмотренной программы hello: Напишите программу, которая выводила бы на консоль название факультета, где учитесь, номер группы, свою фамилию, имя и отчество в разных строках дисплея (консоли) с помощью одной функции printf(). Вывод выполните с помощью нескольких функций printf() (количество функций должно соответствовать каждой порции информации). Для задания пункта 2 вывод информации выполните в различных строках подряд, т. е. без межстрочного пропуска. Проверьте программу без ключевого слова void для функции main().Примечание. Вывод требуемой информации осуществляется с помощью букв латинского алфавита. Комментарии в программе могут быть сделаны после символа "//" или внутри комбинации символов "/* */". Контрольные вопросыКакие компиляторы языка С вам известны? Какое имя имеет исполняемый файл созданного проекта? Объясните назначение заголовочных файлов stdio. h, conio. h. Как будет работать программа без заголовочного файла conio. h? В каком месте программы находится точка ее входа? Как осуществляется табуляция строки на консоли и на сколько позиций выполняется отступ от левого края? Какое значение имеет главная функция проекта main() в программах на языке С? |
|
Программирование на языке C в Microsoft Visual Studio 2010 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
2. Лекция: Переменные и базовые типы данных языка С: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Теоретическая частьФундаментальные объекты данных, с которыми работает программа, – это переменные и константы [2.1]. Используемые в программе переменные перечисляются в объявлениях или декларациях, в которых указывается их тип, а также иногда их начальные значения. С именами переменных связывается тип данных, который контролируется компилятором и для которого выделяется определенное количество байтов памяти [2.1]. Имена переменных должны начинаться с буквы (латинского алфавита) или символа подчеркивания (например, _aza), за которым могут следовать любые комбинации букв в любом регистре (заглавные или строчные), символы подчеркивания или цифры 0–9. В языке С имеется различие между заглавными и строчными буквами. Поэтому переменная World будет отличаться от переменной world и т. п. При этом в определении переменной не разрешается символ пробела (пробелов) и некоторые другие символы, например, $... . Стандарт С89 определяет пять базовых типов данных [2.4]: int – целочисленный тип, целое число; float – вещественное число одинарной точности с плавающей точкой; double – вещественное число двойной точности с плавающей точкой; char – символьный тип для определения одного символа; void – тип без значения. Кроме того, существуют модификаторы, которые могут применяться к этим базовым типам [2.1]. Ряд компиляторов может поддерживать еще и логический тип _Bool. Тип void служит для объявления функции, не возвращающей значения, или для создания универсального указателя (pointer). Объект типа char всегда занимает 1 байт памяти [2.4]. Размеры объектов других типов, как правило, зависят от среды программирования и операционной системы. Приведем модификаторы базовых типов данных. К ним относятся следующие спецификаторы, предшествующие им в тексте программы [2.4]: signed, unsigned, long, short Базовый тип int может быть модифицирован каждым из перечисленных спецификаторов. Тип char модифицируется с помощью unsigned и signed, тип double – с помощью long. В табл. 2.1 приведены допустимые комбинации типов данных языка С с их минимальным диапазоном значений и типичным размером [13].
Для базового типа int возможны следующие записи с модификатором: signed или signed int unsigned или unsigned int long или long int short или short int Для данных вещественного типа максимальные значения абсолютных величин представлены в табл. 2.2 [2.4].
В языке С предусматривается преобразование типов в выражениях и приведение типов. Если в выражении смешаны различные типы литералов и переменных, то компилятор преобразует их в один тип. Во-первых, все char и short int значения автоматически преобразуются (с расширением "типоразмера") в тип int. Этот процесс называется целочисленным расширением (integral promotion). Во-вторых, все операнды преобразуются (также с расширением "типоразмера") в тип самого большого операнда. Этот процесс называется расширением типа (type promotion), причем он выполняется пооперационно. Например, если один операнд имеет тип int, а другой–long int, то тип int расширяется в тип long int. Или если хотя бы один из операндов имеет тип double, то любой другой операнд приводится к типу double. Это означает, что такие преобразования, как тип char в тип double, вполне допустимы (если предусматривать, к чему это приведет). После преобразования оба операнда будут иметь один и тот же тип, а результат операции – тип, совпадающий с типом операндов. Приведем последовательность преобразования типов в выражениях по старшинству [2.4]: ЕСЛИ операнд имеет тип long double ТО второй операнд преобразуется в long double ИНАЧЕ ЕСЛИ операнд имеет тип double ТО второй операнд преобразуется в double ИНАЧЕ ЕСЛИ операнд имеет тип float ТО второй операнд преобразуется в float ИНАЧЕ ЕСЛИ операнд имеет тип unsigned long ТО второй операнд преобразуется в unsigned long ИНАЧЕ ЕСЛИ операнд имеет тип long ТО второй операнд преобразуется в long ИНАЧЕ ЕСЛИ операнд имеет тип unsigned int ТО второй операнд преобразуется в unsigned int Кроме того, действует правило: если один из операндов имеет тип long, а второй– unsigned int, притом значение unsigned int не может быть представлено типом long, то оба операнда преобразуются в значение типа unsigned long. В языке С предусматривается явное преобразование (приведение) типов. Общая форма оператора явного приведения типа: (тип) выражение. В приведенной форме тип – это любой поддерживаемый тип данных. Явное преобразование типа – это операция. Оператор приведения типа является унарным и имеет тот же приоритет, что и остальные унарные операторы. В приводимых ниже программах используются такие средства ввода-вывода, как printf(), getchar(), gets(), scanf(). Приведем характеристику данных функций [2.4]. Прототип функции printf()имеет вид: int printf(const char *format, ?); Функция printf() записывает в стандартный поток stdout (стандартный выходной поток данных) значения аргументов из заданного списка аргументов в соответствии со строкой форматирования, адресуемой параметром format. Строка форматирования состоит из элементов двух типов. К элементам первого типа относятся символы, которые выводятся на экран. Элементы второго типа содержат спецификации формата, определяющий способ отображения аргументов. Спецификация формата начинается символом процента, за которым следует код формата. На спецификации формата могут воздействовать модификаторы, задающие ширину поля, точность и признак выравнивания по левому краю. Целое значение, расположенное между знаком % и командой форматирования, играет роль спецификации минимальной ширины поля. Наличие этого спецификатора приводит к тому, что результат будет заполнен пробелами или нулями, чтобы выводимое значение занимало поле, ширина которого не меньше заданной С І минимальной ширины. По умолчанию в качестве заполнителя используется пробел (пробелы). Для заполнения нулями перед спецификацией ширины поля нужно поместить нуль, т. е. 0. Например, спецификация формата %05d дополнит нулями выводимое целое число, в котором менее пяти цифр, чтобы общая длина равнялась пяти символам. Действие модификатора точности зависит от кода формата, к которому он применяется. Чтобы добавить модификатор точности, следует поставить за спецификатором ширины поля десятичную точку, а после нее – требуемое значение точности (число знаков после десятичной точки). Применительно к целым числам модификатор точности задает минимальное количество выводимых цифр. При необходимости перед целым числом будут добавлены нули. Если модификатор точности применяется к строкам, то число, следующее за точкой, задает максимальную длину поля. Например, спецификация %5.7s выведет строку длиной не менее пяти, но не более семи символов. Если выводимая строка окажется длиннее максимальной длины поля, конечные символы будут отсечены. По умолчанию все выводимые значения выравниваются по правому краю: если ширина поля больше выводимого значения, то оно будет выровнено по правому краю поля. Чтобы установить выравнивание по левому краю, нужно поставить знак "минус" ("–") сразу после знака процента. Например, спецификация формата %–10.4f обеспечит выравнивание вещественного числа с четырьмя десятичными знаками по левому краю в 10-символьном поле. Существуют два модификатора формата, позволяющие функции printf() отображать короткие и длинные целые числа. Это модификатор l (латинская буква эль) уведомляет функцию printf() о длинном типе значения. Модификатор h сообщает функции printf(), что нужно вывести число короткого целого типа. Кроме того, модификатор l можно поставить перед командами форматирования вещественных чисел. В этом случае он уведомит о выводе знач ения типа long double. Спецификаторы формата для функции printf() перечислены в табл. 2.3, взятой из [2.3].
Прототип функции getchar() имеет следующий вид: int getchar(void); Функция getchar() возвращает из стандартного потока stdin (входного потока данных) следующий символ. При чтении символа предполагается, что символ имеет тип unsigned char, который потом преобразуется в целый. При достижении конца файла, как и при обнаружении ошибки, функция getchar() возвращает значение EOF (End Of File – конец файла). Прототип функции gets имеет следующий вид: char *gets(char *str); Функция gets() читает символы (включая пробелы) из стандартного потока stdin и помещает их в массив символов, адресуемый указателем *str (далее это массив символов). Символы читаются до тех пор, пока не встретится разделитель строк или значение EOF. Для реализации EOF на клавиатуре следует набрать одновременно Ctrl+Z. Вместо разделителя строк в конец строки вставляется нулевой символ, свидетельствующий о ее завершении. Следует учесть, что нет способа ограничить количество символов, которое прочитает функция gets(). Поэтому массив, адресуемый указателем *str, может переполниться, и тогда программа выдаст непредсказуемые результаты. Прототип функции scanf() имеет следующий вид: int scanf(const char *format, ?); Функция scanf() представляет собой функцию для ввода данных общего назначения, которая читает поток stdin и сохраняет информацию в переменных, перечисленных в списке аргументов. Если в строке форматирования встретится разделитель, то функция scanf() пропустит один или несколько разделителей во входном потоке. Под разделителем, или пробельным символом, подразумевают пробел, символ табуляции \t или разделитель строк \n. Все переменные должны передаваться посредством своих адресов, например, с помощью символа &. Управляющая строка, задаваемая параметром format, состоит из символов трех категорий: спецификаторов формата, пробельных символов, символов, отличных от пробельных [2.4]. Спецификация формата начинается знаком % и сообщает функции scanf() тип данного, которое будет прочитано. Спецификации формата функции scanf() приведены в табл.2.4.
Строка форматирования читается слева направо, и спецификации формата сопоставляются с аргументом в порядке их перечисления в списке аргументов. Символ *, стоящий после знака % и перед кодом формата, прочитает данные заданного типа, но запретит их присваивание. Команды форматирования могут содержать модификатор максимальной длины поля. Он представляет собой целое число, располагаемое между знаком % и кодом формата, которое ограничивает количество читаемых для всех полей символов. Если входной поток содержит больше заданного количества символов, то при последующем обращении к операции ввода чтение начнется с того места, в котором "остановился" предыдущий вызов функции scanf() [2.3]. Если разделитель (например, пробел) встретится раньше, чем достигнута максимальная ширина поля, то ввод данных завершится. В этом случае функция scanf() переходит к чтению следующего поля. При чтении одиночных символов символы табуляции и разделители строк читаются подобно любому другому символу. В программах бывает необходимость определять константы. В языке С типы констант можно задавать явно при использовании суффиксов. Например: long int j = –L; /* суффикс L */ unsigned int a = 678U; /* суффикс U */ float x = 123.45F; /* суффикс F */ long double z = .99L; /* суффикс L* / По умолчанию спецификации f, e, g заставляют функцию scanf() присваивать переменным типа float. Если перед одной из этих спецификаций поставить модификатор l, то функция scanf() присвоит прочитанные данные переменной типа double. Функция scanf() поддерживает спецификатор формата общего назначения, называемый набором сканируемых символов. В этом случае определяется набор символов, которые могут быть прочитаны функцией scanf() и присвоены соответствующему массиву символов. Для определения такого набора символы, подлежащие сканированию, необходимо заключить в квадратные скобки. Открывающая квадратная скобка должна следовать сразу за знаком процента. При использовании набора сканируемых символов функция scanf() продолжает читать символы и помещать их в соответствующий массив символов до тех пор, пока не встретится символ, отсутствующий в данном наборе. Если первый символ в наборе является знаком "^", то получится обратный эффект: входное поле читается до тех пор, пока не встретится символ из заданного набора сканируемых символов, т. е. знак "^" заставляет функцию scanf() читать только те символы, которые отсутствуют в наборе сканируемых символов. Если в строке форматирования встретился символ, отличный от разделителя, то функция scanf() прочитает и отбросит его. Если заданный символ не найден, то функция scanf() завершает работу. В таких средах разработки как MS Visual Studio 2008 и 2010 рекомендуется для безопасной работы применять функции gets_s() и scanf_s(). Для этих функций при чтении символа или строки следует указать размер в байтах, соответственно для символа или строки. Например, scanf_s("%c", &ch, 1). В Visual Studio 2010 тип данных char занимает 1 байт. Практическая частьДля приводимых примеров будем приводить только текст программы и результат ее выполнения. Программы должны быть написаны в Visual Studio (см. тему 1). Пример 1. Напишите программу ввода символа, строки, действительных и целых чисел. Действительные числа сложите, целые числа перемножьте. Для действительных чисел использовать типы float и double. Программный код решения примера: #include <stdio. h> #include <conio. h> int main (void) { // Объявления char ch, str[79+1];// С учетом одного места для символа '\0' int x, y, z; float a, b, c; double A, B, C; // Выполнение программы printf("\n\t Enter a symbol: "); ch = getchar(); printf("\t The symbol is: %c\n", ch); _flushall(); printf("\n\t Enter a string: "); gets_s(str, 79); printf("\t The string is: %s\n", str); a = 2.42F; b = 3.58F; c = a + b; printf("\n\t The sum %1.2f and %1.2f (as float) is equal: %1.4f\n", a, b, c); A = 12.; B = 2.; C = A + B; printf("\n\t The sum %1.13f and %1.13f \n\t is equal (as double): %1.13f\n", A, B, C); x = 2; y = 7; z = x*y; printf("\n\t Multiplication %d on %i (as an integer) is equal: %d\n", x, y, z); printf("\n\n Press any key: "); _getch(); return 0; } В объявлении символьного массива str[79+1] в методических целях сделано напоминание, что для строки следует предусмотреть символ ее завершения, т. е. '\0'. В определении переменных типа float добавлены суффиксы F.
Результат выполнения программы показан на рис. 2.1. Добавим считывание данных с помощью функции scanf_s(). В результате получим следующую программу: #include <stdio. h> #include <conio. h> int main (void) { // Объявления char ch, str[79+1]; int x, y, z; float a, b, c; double A, B, C; // 1-я часть printf("\n\t Enter a symbol: "); ch = getchar(); printf("\t The symbol is: %c\n", ch); _flushall(); printf("\n\t Enter any string: "); gets_s(str, 79); printf("\t The string is: %s\n", str); a = 2.42F; b = 3.58F; c = a + b; printf("\n\t The sum %1.2f and %1.2f (as float) is equal: %1.4f\n", a, b, c); A = 12.; B = 2.; C = A + B; printf("\n\t The sum %1.13f and %1.13f \n\t is equal (as double): %1.13f\n", A, B, C); x = 2; y = 7; z = x*y; printf("\n\t The multiplication %d on %i (as an integer) is equal: %d\n", x, y, z); // 2-я часть printf("\n\t Enter a symbol: "); scanf_s("%c", &ch, 1); printf("\t The symbol is: %c\n", ch); printf("\n\t Enter any string: "); scanf_s("%s", str, 79); printf("\t The string is: %s\n", str); _flushall(); // для устранения пустой строки printf("\t Enter a real number a: "); scanf_s("%f", &a); printf("\t Enter a real number b: "); scanf_s("%f", &b); c = a + b; printf("\t The sum %1.2f and %1.2f (as float) is equal: %1.4f\n", a, b, c); printf("\n\t Enter a real number A: "); scanf_s("%lf", &A); printf("\t Enter a real number B: "); scanf_s("%lf", &B); C = A + B; Результат выполнения видоизмененной программы показан на рис. 2.2.
В начале функции int main(void) сделано объявление переменных, которые будут использоваться в программе. Каждый тип переменных объявлен через запятую. В частности, char str[81] означает массив символов, в квадратных скобках указано их число. Функции printf()выводят либо только сообщения, либо еще заданные переменные соответствующих типов. Функция _flushall() используется для того, чтобы устранить пробельную строку, образованную либо после действия функции getchar(), либо после действия функции scanf(). В некоторых компиляторах функция _flushall() используется без префиксного нижнего подчеркивания. Функция gets() позволяет считывать символы с наличием разделителей, в частности, с пробелами. В Microsoft Visual Studio 2010 рекомендуется использовать gets_s(), чтобы не было предупреждений. Следует обратить внимание на формат записи функций scanf(). Если сканируются числа, или одиночные символы, то присваивание этих символов переменным, которые были объвлены через соответствующие типы данных, осуществляется с помощью взятия адреса этих переменных, т. е. с помощью символа &, например, scanf_s("%c",&ch, 1). При сканировании массива символов, т. е. при сканировании строки, символ & не используется. Имя массива само по себе является указателем. Обращение к адресу осуществляется с помощью указателей (будут рассмотрены позднее). Для сканирования чисел типа double в функции scanf_s() используется спецификатор l. Задание 1 В качестве вводимых символов используйте начальные буквы своей фамилии (латинского алфавита). В отчет вставьте полученный результат. В качестве вводимой строки используйте свою фамилию и имя (буквами латинского алфавита). В отчет вставьте полученный результат. Подсчитайте количество символов, на которые производит отступ от левого края символ табуляции \t. Для вывода строки предусмотрите табулированный вывод с помощью спецификатора формата %xs, где х – требуемое число позиций отступа. Объявите массив символов в размере одного символа и введите строку с помощью функции gets_s(), превышающую число 1. Запустите программу без функций _flushall(). В отчет вставьте полученный результат Ввод чисел произведите со знаком ("+" и "–"). Введите только целые числа. Введите только вещественные числа. Сложите два вещественных числа типа float с десятью знаками после десятичной точки. Выведите и проанализируйте результат сложения. Объясните результат. Изучите работу функции puts() и примените ее вместо функции printf(), которая использовалась в режиме сообщений. В отчет вставьте полученный результат. Объясните результат.Пример 2. Напишите программу вычисления алгебраических выражений с приведением типов char, int, float, double. Возможный программный код решения примера: #include <stdio. h> #include <conio. h> #include <math. h> int main (void) { char ch; int a, b; float x; double y, z; ch = 'C'; a = 2; b = 5; x = 5.5F; y = 6.0 ; z = ch + a + b + x + y; // Результат приведения типов printf("\n\t The result of the conversion types: %lf\n", z); // Явное преобразование типов z = (double) ch + (double) a + (double) b + (double) x + y; printf("\n\t The apparent conversion types: %lf\n", z); z = sqrt((double)a/b); printf("\n\t z = sqrt(%d/%d) = %lf\n", a, b, z); printf("\n\t z = lg(%d/%d) = %lf\n", a, b, log10(z)); printf("\n\t z = ln(%d/%d) = %lf\n", a, b, log(z)); printf("\n Press any key: "); _getch(); return 0; } В программу включена библиотека математических функций math. h, в которой sqrt() – функция извлечения квадратного корня, log10() – логарифмическая функция по основанию 10, log() – функция натурального логарифма. Все эти функции возвращают результат типа double и вычисляют от числа (выражения) также типа double. Результат выполнения программы показан на рис. 2.3.
Задание 2 В качестве вводимого символа используйте первую букву своей фамилии (буквами латинского алфавита). В отчет вставьте полученный результат. Переменную у задайте в виде у = 6. Вычислите заданный квадратный корень без явного приведения типов. Определите переменные с суффиксами U, F, L и произведите с ними арифметические действия. Вычислите логарифм от числа 10Х по основанию 2Х, где Х – номер компьютера, за которым выполняется лабораторная работа. Выполните тестовый пример для проверки вычислений, т. е. для заданного основания подберите число, от которого логарифм с заданным основанием даст целое число. Например, log10(100.0) = 2.0000.Пример 3.Напишите программу вычисления площади круга и его длины окружности по заданному радиусу, вводимого пользователем с клавиатуры, а также вывода на консоль максимальных значений чисел типа int, float и double. Для решения примера следует воспользоваться математической библиотекой компилятора, т. е. включить в программу заголовочный файл <math. h>., а также заголовочные файлы <limits. h>, <float. h>. Программный код решения примера: #include <stdio. h> #include <conio. h> // Для числа пи ( #define _USE_MATH_DEFINES #include <math. h> #include <limits. h> #include <float. h> int main (void) { double R; double Sr, Lr; printf("\n Enter a real greater than zero: "); scanf_s("%lf", &R); Sr = M_PI*R*R; Lr = 2*M_PI*R; printf("\n Area of a circle of radius R = %g is %g", R, Sr); printf("\n The length of a circle of radius R = %g is %g",R, Lr); puts(""); printf("\n Maximum integer: %d\n ", INT_MAX); printf(" Maximum real number of float: %g\n ", FLT_MAX); printf("Maximum real number type double: %g\n ", DBL_MAX); printf("\n Press any key: "); _getch(); return 0; } В программу включена константа _USE_MATH_DEFINES для работы с числом M_PI ( Функция scanf_s() определена в компиляторе языка С системы MS Visual Studio 2008. С этой функцией компилятор не выдает предупреждений. Результат выполнения программы показан на рис. 2.4.
Задание 3 Напишите программу по вычислению площади эллипса. Данные для расчета должны вводиться с клавиатуры пользователем. Рассчитайте величиныКонтрольные вопросыДля каких типов данных используются суффиксы при инициализации переменных? Чем отличаются функции printf() и puts() при консольном выводе информации? Для чего в программах на С используется заголовочный файл math. h? При использовании функции gets_s()с какими разделителями может происходить считывание информации с консоли? Какой тип данных возвращает функция gets_s() при считывании информации? Как осуществляется считывание с консоли информация с помощью функции scanf_s()? Как с консоли осуществляется считывание последовательности различных типов данных с помощью одной функции scanf_s()? Как выводится на консоль последовательность различных типов данных с помощью одной функции printf()? Как осуществляются автоматическое и принудительное приведение типов в языке С? Какие машинно-зависимые типы данных имеются в языке С? К каким типам данных относятся литеры5, 5.0, 5.0F, "5", '5', 5u, 5L, 5.0L? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Программирование на языке C в Microsoft Visual Studio 2010 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
3. Лекция: Организация циклов в языке С: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Теоретическая часть Операторы цикла относятся к управляющим конструкциям всякого языка программирования. Управляющие операторы и конструкции языка задают порядок, в котором выполняются вычислительные операции программы [3.1]. 3.1. Оператор while Изучение операторов цикла начнем с оператора while. Цикл while имеет следующий формат (синтаксис) записи [3.2]: while (expression) program statement; Производится расчет выражения expression, заключенного в круглые скобки. Если в результате расчета выражения expression получается истинный результат (TRUE), то выполняется утверждение program statement, следующее непосредственно за закрывающей круглой скобкой. После выполнения этого утверждения вновь рассчитывается выражение expression. Если в результате расчета будет TRUE, то вновь будут выполнены утверждения program statement. Цикл повторяется до тех пор, пока в результате расчета выражения expression (в круглых скобках оператора while) не будет получено значение FALSE (ложный), которое является признаком окончания цикла, после чего выполнение программы продолжается с утверждения, следующего за утверждением program statement. Когда требуется выполнить группу утверждений, то она (группа) располагается в фигурных скобках: while (expression) { program statement; program2 statement2; program3 statement3; ... } Открывающаяся фигурная скобка может следовать непосредственно после закрывающей круглой скобки оператора while. Все, что находится в фигурных скобках, будет выполняться, пока верно выражение expression. Очевидно, что неверное задание выражения expression может привести к бесконечному циклу (к зацикливанию). 3.2. Оператор for Оператор цикла for имеет следующий формат записи: for (init_expression; loop_condition; loop_expression) program statement; Три выражения, заключенные в круглые скобки оператора цикла for, задают условия выполнения программного цикла [3.2]. Первый параметр init_expression используется для задания начального значения цикла. Второй компонент loop_condition определяет условие или условия, в соответствии с которыми будет происходить выход из цикла. Повторение будет происходить до тех пор, пока это условие (или условия) выполняются. Если условие не выполняется, то цикл немедленно заканчивается. Третий параметр loop_expression выполняется каждый раз, когда заканчивается обработка тела цикла, т. е. program statement. Чаще всего выражения init_expression и loop_expression являются операторами присваивания или вызовами функций, а второе выражение loop_condition – выражением отношения или логическим выражением [3.1]. Любую из трех частей можно опустить, но точки с запятыми должны остаться на своих местах. Если опустить init_expression или loop_expression, то соответствующие операции не будут выполняться. Если же опустить проверку условия loop_condition, то по умолчанию считается, что условие продолжения цикла всегда истинно, и тогда цикл станет бесконечным (произойдет зацикливание). Когда требуется выполнения нескольких утверждений, то они должны заключаться в фигурные скобки: for (init_expression; loop_condition; loop_expression) { program1 statement1; program2 statement2; program3 statement3; ... } В представленном случае тело цикла находится в фигурных скобках. Конструкция цикла, реализованная оператором for, может быть выполнена также и оператором while следующим образом [3.1]: init_expression; while (loop_condition) { program statement; loop_expression; } Исключением является применение операции continue. Операторы отношения перечислены в табл. 3.1.
Больше или равно В программах языка С возможно применять вложенные циклы, каждый из которых контролируется своей переменной цикла и своим отношением (второе выражение в круглых скобках оператора for). Вложенные циклы могут идти непосредственно друг за другом или составлять тело цикла с помощью фигурных скобок. Возможно также использование двух индексных переменных для инициализации начала цикла с последующим их инкрементированием (увеличением) или декрементированием (уменьшением). 3.3. Оператор do–while Рассмотренные операторы цикла while и for производят проверку условия выполнения цикла до начала выполнения тела цикла [3.1]. Поэтому тело цикла может ни разу не выполниться, если с самого начала результатом расчета условия выполнения цикла будет значение FALSE (ложь). В случае необходимости производить проверку условия выполнения цикла после тела цикла (т. е. когда выполняется хотя бы одно предписанное действие в теле цикла) прибегают к циклу do–while. Оператор цикла do–while имеет следующий формат записи [3.2]: do program statement; while (loop_expression); Выполнение цикла do–while происходит следующим образом: сначала выполняется утверждение program statement, затем производится проверка условия выполнения цикла loop_expression с помощью оператора while. Если результатом проверки будет значение TRUE (истина), то выполнение цикла продолжится, и утверждение program statement всякий раз будет выполняться вновь. Повторение цикла будет продолжаться до тех пор, пока в результате проверки условия выполнения цикла loop_expression будет получаться значение TRUE. Когда в результате проверки условия будет вычислено значение FALSE (ложь), то выполнение цикла прекратится и произойдет переход к утверждению (следующему фрагменту программы), непосредственно следующему за циклом [3.2]. Таким образом, цикл do–while гарантированно выполнится хотя бы один раз. В случае выполнения нескольких утверждений используются фигурные скобки для выделения тела цикла: do { program1 statement1; program2 statement2; program3 statement3; ... } while (loop_expression); Оператор цикла while называется оператором цикла с предусловием, оператор цикла for называется оператором цикла с параметром, оператор цикла do–while называется оператором цикла c постусловием. Практическая часть Рассмотрим примеры программ с операторами циклов while, for и do–while. Пример 1. Напишите программу вывода на экран пользователя целых положительных чисел с помощью оператора while. Начальное и последнее число должно задаваться пользователем с клавиатуры. Программный код решения примера: #include <stdio. h> #include <conio. h> int main(void) { int i, j = 0, n; printf("\n\t Enter the primary natural number: "); scanf_s("%d", &i); printf("\t Enter the last natural number: "); scanf_s("%d", &n); printf("\n\t The numbers are:"); while (i <= n) { printf("\n\t %3d", i); ++i; ++j; } printf("\n\t Total numbers: %d\n", j); printf("\n Press any key: "); _getch(); return 0; }
Возможный результат выполнения программы показан на рис. 3.1. В программе использована функция scanf_s(), принятая в MS Visual Studio. В программе применено инкрементирование переменных, принятое в языке С, а именно ++i или ++j означает, что переменные увеличиваются на единицу. При этом знаки "++" могут располагаться перед именем переменной или после. Отличие в том, что ++i – это значение переменной после увеличения, а i++ – сначала переменная имеет заданное значение, а потом происходит ее увеличение. Для переменных цикла обе формы равнозначны. Условием цикла является то, что пока переменная i меньше или равна переменной n (предполагается, что n больше начального значения i), то будут выполняться действия (печать и увеличение переменной j), заложенные в теле цикла. Расчет выражения, заключенного в круглые скобки оператора, предназначен для проверки нестрогого неравенства переменной i по отношению к переменной n. Если это неравенство выполняется, то в теле цикла происходят печать и увеличение (инкрементирование) переменных i, j. Задание 1 Предусмотрите ввод только отрицательных чисел. Предусмотрите ввод только неотрицательных чисел. Предусмотрите вывод чисел и их порядковые номера (т. е. в два столбца). Предусмотрите вывод чисел на консоль в виде строки. Предусмотрите ввод и вывод только вещественных чисел. С учетом предыдущего пункта предусмотрите вывод чисел и их порядковые номера (т. е. в два столбца). Рассмотреть варианты форм инкрементирования. Выполните вывод на консоль.Пример 2. Напишите программу посимвольного ввода предложения "Hello, world" и подсчитать число символов в нем (включая запятую и пробел). Программный код решения примера: #include <stdio. h> #include <conio. h> int main(void) { int c, i = 0; printf("\n Enter symbol-by-symbol the offer \"Hello, world\",\n press twice Ctrl+Z and press Enter:\n"); printf("\t"); while ((c = getchar()) != EOF) { printf("\t"); c = getchar(); ++i; } printf("\n\t The number of characters: %d\n", i); printf("\n Press any key: "); _getch(); return 0; } В программе ввод символьных данных должен завершиться комбинацией клавиш Ctrl+Z, что будет соответствовать окончанию ввода (файла), т. е. EOF. Оператор цикла while будет выполняться до тех пор, пока не встретится так называемый конец файла EOF. В предложении \"Hello, world\" два обратных слэша включены для вывода на консоль двойных кавычек. Функции printf("\t") осуществляют табуляцию вводимых символов. Подсчет числа вводимых символов выполняется с помощью переменной цикла (счетчика) i, которая инкрементируется в теле цикла. Заголовочный файл #include <conio. h> служит для поддержания консольного ввода-вывода для функции _getch(). Без него при компиляции программы могут выводиться предупреждения, хотя программа с предупреждениями работает. Результат выполнения программы показан на рис. 3.2.
Задание 2 В программу введите комментарии с помощью символов // и /*...*/. В качестве вводимых символов используйте буквы своей фамилии (буквами латинского алфавита). В отчет вставьте полученный результат. В качестве вводимых символов использовать свою фамилию и имя. В отчет вставьте полученный результат. Сделайте вывод своей фамилии и имени по главной диагонали дисплея. В качестве символов введите числа от 1 до (2+Х), где Х – номер компьютера, за которым выполняется лабораторная работа. В отчет вставьте полученный результат. Выполните дублирование символов с помощью функции putchar(), которая отображает символы на экране пользователя.Примечание. Прототип функции putchar() имеет следующий вид: #include <stdio. h> int putchar (int ch); Пример 3. Напишите программу табличного вывода строчных букв латинского алфавита и их десятичных кодов с помощью оператора цикла for. Как известно, в латинском алфавите 26 букв. Поэтому можно создать массив символов этих букв. С учетом того, что тип char представляет собой целочисленный тип, то можно обойтись без создания массива. Программный код решения примера: #include <stdio. h> #include <conio. h> int main(void){ int j = 1; char a = 'a'; printf("\n Table code characters:\n"); for ( ; a <= 'z'; ++a) printf("\n %4d) %2c: code%4d", j++, a, a); printf("\n\n Press any key: "); _getch(); return 0; } Результат выполнения программы показан на рис. 3.3.
Форматированный вывод данных предусматривает выравнивание по правому краю, для чего предусматриваются числовые спецификаторы типа %4d и %2c для целых чисел и символов в функции printf(). Задание 3 Примените префиксную форму инкрементирования переменной j. Инкрементирование переменной j создайте отдельно в теле цикла. Инициализацию переменной j начните с нуля. Результат выполнения программы должен быть тот же самый, что и в приведенной программе. Напишите программу для вывода кодов букв из заданного диапазона на усмотрение пользователя, например, от 'j' до 'w'. В цикле for заполните все поля. Напишите программу для вывода кодов букв латинского алфавита прописных и строчных букв в одной таблице. Выведите буквы своей фамилии и коды этих букв.Напишите программу расчета значений функции распределения потока Эрланга 4-го порядка с параметром
Программный код решения примера: #include <stdio. h> #include <conio. h> #include <math. h> int main(void) { int j, f, k = 4; double F, Lt, s, t = 0.0, Tend = 5.0; float L = 2.3F; printf("\n Erlang function of order %d, Lambda = %1.2f:\n\n", k, L); for ( ; t <= Tend; t += 0.2) { s = 0.0; f = 1; Lt = 1.0; for (j = 1; j <= k; ++j) { f *= j; Lt *= (L*t); s += (Lt/f)*exp(-L*t); } F = 1 - exp(-L*t) - s; printf("\t %lg\n", F); } printf("\n\n Press any key: "); _getch(); return 0; } В программе использованы вложенные циклы for. Применены специфические условия изменения переменных (но характерных для языка С): t += 0.2; /* t = t + 0.2; */ f *= j; /* f = f * j; */ Lt *= (L*t); /* Lt = Lt * (L*t); */ Вычисление факториала выполнено с помощью произведения и выделения отдельного слагаемого суммы, когда рассчитывается 0! В программе также предусмотрено накопление суммы для каждого шага времени t. Для расчета экспоненты в заголовок программы включена библиотека математических функций #include <math. h>. Результат выполнения программы показан на рис. 3.4.
Задание 4 В заголовке внешнего цикла for добавьте первое поле и устраните третье поле. Предусмотрите вывод количества значений рассчитанной функции Эрланга. Предусмотрите ввод параметров программы, а именно: порядок потока Эрланга k, параметрПример 5. Вычислите с точностью до "машинного нуля" значение суммы числового ряда:
Программный код решения примера: #include <stdio. h> #include <conio. h> int main(void) { double denom; double sum1 = 0.0, sum2 = 0.0; int k = 1; denom = k * (k + 1) * (k + 2); // знаменатель ряда do { sum1 = sum2; sum2 += 1.0 / denom; denom = denom / k * (k + 3); ++k; } while (sum1 < sum2); printf("\n\t The amount of numerical series: %lg\n", sum2); printf("\n Press any key: "); _getch(); return 0; } В приведенной программе сумма вычисляется как значение переменной sum2. Ее предыдущее значение сохраняется в переменной sum1. Так как приближенное значение с добавлением неотрицательных слагаемых не уменьшается, условием продолжения цикла служит отношение sum1 < sum2 (поскольку растет знаменатель denom). Когда при добавлении очередного слагаемого значение суммы остается неизменным (за счет конечной разрядной сетки для представления вещественных чисел), нарушается условие sum1 < sum2 и цикл прекращается. Таким образом, конечность разрядной сетки представления вещественных чисел в компьютере определяет собой "машинный нуль". Инициализация знаменателя сделана до начала цикла. Форматный вывод результата выполнен с помощью спецификатора символа "l". Результат выполнения программы показан на рис. 3.5.
Задание 5 Подсчитайте число итераций цикла для подсчета суммы ряда. Напишите программу с нулевой инициализацией переменной k. Напишите условие "загрубения" результата подсчета суммы ряда. В программе примените тип float вместо типа double. Проанализируйте результат. Вместо оператора цикла do–while примените иной оператор цикла.Пример 6. Произведите реверс цифр заданного целого числа, вводимого с клавиатуры пользователем. Задача заключается в том, чтобы, например, число 123 переписать как 321. Программный код решения примера: #include <stdio. h> #include <conio. h> int main(void) { long int x, r; printf("\n Enter an integer: "); scanf_s("%ld", &x); printf("\n Reverse-digit number %ld:\n\n\t", x); do { r = x % 10; printf(" %ld", r); x = x / 10; } while ( x!= 0); printf("\n\n Press any key: "); _getch(); return 0; } В программе применена операция арифметическая операция деления по модулю, которая имеет символ процента, т. е. "%". Любой остаток, получающийся в результате деления целых чисел, будет отброшен [3.2]. В шкале старшинства оператор деления по модулю имеет приоритет, равный приоритету операторов умножения и деления. Переменные, используемые в программе, объявлены как длинные числа, поэтому применен тип long int (или long). В некоторых компиляторах имеются отличия между типами int и long int в смысле максимально поддерживаемого значения числа. Результат выполнения программы показан на рис. 3.6.
Задание 6 Предусмотрите подсчет итераций заданного цикла. Напишите программу по реверсу числа с оператором цикла while. Протестируйте обе программы, в том числе по вводу числа 0. Определите максимальное число вида 123...987..., для которого еще можно применить тип long int.Пример 7. На основе только оператора цикла for напишите программу по выводу "горки" заглавных букв, симметрично убывающих к букве, введенной пользователем. Также на основе оператора цикла for предусмотрите защиту от неправильного ввода. Программный код решения примера: #include <stdio. h> #include <conio. h> int main (void) { int p = 0; char ch = 'A'; char i, j, k, ch2, kk, chA; chA = ch; printf("\n Enter a capital letter between \"A\" and \"S\": "); scanf_s("%c", &ch2, sizeof(char)); for(chA -= 1; chA >= ch2; chA-- ) { printf("\n Error! Press any key: "); _getch(); return -1; } for (kk = 'S'+1; kk <= ch2; kk++) { printf("\n Error! Press any key: "); _getch(); return -1; } k = ch2; for ( kk = ch; kk <= k; kk++) { printf("\n "); for (ch2 = ch; ch2 <= k-p ; ch2++) printf(" "); for (j = ch; j <= kk ; j++) printf(" %c", j); for (i = kk; i > ch; i-- ) printf(" %c", i-1); p++; } printf("\n\n Press any key: "); _getch(); return 0; } Пример выполнения программы показан на рис. 3.7.
Задание 7 Напишите программу на основе только оператора цикла for по выводу перевернутой горки букв, а также "левой" и "правой" горки. Напишите программу на основе только оператора цикла for по выводу "ромба" букв, относительно введенной буквы. Напишите программу по выводу горки букв только на основе оператора цикла while.Контрольные вопросы Как организуются составные операторы циклов в языке С? Как организуются вложенные циклы в языке С? В каких случаях может произойти зацикливание при использовании оператора цикла с предусловием? В каких случаях может произойти зацикливание при использовании оператора цикла с постусловием? Сколько условий требуется для работы оператора цикла с параметром? Чем отличаются префиксное и постфиксное инкрементирование и декрементирование? Какое различие в операторах цикла между префиксным и постфиксным инкрементированием? Сколько операторов отношения в языке С? Перечислите их. Как реализуется взаимозаменяемость операторов цикла while и for? В чем сходство и различие между циклами с предусловием и с постусловием? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Программирование на языке C в Microsoft Visual Studio 2010 | ||||||||||||||
| ||||||||||||||
| ||||||||||||||
| ||||||||||||||
4. Лекция: Принятие решений. Условные операторы в языке С: В лекции рассматриваются операторы if, if–else, if–else if–else, switch–case–default, оператор условия?, операторы перехода break, continue, безусловный оператор перехода goto. Изучаются вложенные условные операторы, а также логические условия. | ||||||||||||||
| ||||||||||||||
| ||||||||||||||
| ||||||||||||||
Теоретическая частьВ языке программирования С используются несколько конструкций для принятия решений:
Для прерывания программного цикла при некотором условии применяется утверждение (оператор) break, для продолжения итераций цикла при выполнении некоторых условий применяется утверждение (оператор) continue, для выхода из функции при выполнении некоторых условий применяется оператор return, для перехода к заданному месту программы применяется оператор goto, хотя считается, что в программировании не существует ситуаций, в которых нельзя обойтись без оператора goto [2; 3]. Утверждение break применяется также в теле оператора switch. 4.1. Оператор ifОбщая форма записи оператора if: if (expression) program statement; В операторе if используется результат вычисления условия, заключенного в круглые скобки, на основе которого принимается решение. Результат вычисления условия expression может быть арифметическим или логическим. Если результат выполнения условия expression будет истинным, то возможно выполнить несколько утверждений типа program statement. Для этого следует использовать фигурные скобки, например: if (expression) { program1 statement1; program2 statement2; ... } 4.2. Конструкция if–elseОбщая форма записи конструкции if–else: if (expression) program1 statement1; else program2 statement2; Если выполняется условие expression, то будет выполняться фрагмент программы program1 statement1, в противном случае будет выполняться program2 statement2. Каждое из утверждений может быть множественным. В таком случае применяются фигурные скобки: if (expression) { program1 statement1; program2 statement2; ... } else { program33 statement33; program34 statement34; ... } 4.3. Конструкция if–else if–else if–...–elseФорма записи конструкции if–else if–else if–...–else: if (expression1) program1 statement1; else if (expression2) program2 statement2; else if (expression3) program3 statement3; ... else program statement; Приведенная конструкция используется для выбора возможных ситуаций, когда проверяются условия expression1, expression2, expression1,... . Соответственно будут выполняться действия program1 statement1, program2 statement2, program3 statement3 и т. д. В случае, когда ни одно из условий не выполняется, выполняются действия, прописанные после оператора else. В случае выполнения множественных действий применяются фигурные скобки для каждого из утверждений: if (expression1) { program1 statement1; ... } else if (expression2) { program2 statement2; ... } else if (expression3) { program3 statement3; ... } ... else { program statement; ... } 4.4. Оператор switchОбщая форма записи оператора switch: switch (expression) { case value1: program statement; ... break; case value2: program statement; ... break; ... case valuen: program statement; ... break; default: program statement; ... break; } Выражение заключенного в круглые скобки оператора последовательно сравнивается со значениями value1, value2,..., valuen, которые должны быть простыми константами или константными выражениями. В том случае, когда одно из этих значений равно значению, выполняются утверждения, которые следуют за данным значением. Утверждение break сигнализирует об окончании выполнения утверждений и приводит к выходу из оператора switch. Утверждение break ставится в конце каждого варианта выбора. Если этого не сделать, то выполнение последовательности утверждений перейдет в следующий вариант выбора и будет выполняться до тех пор, пока не встретится утверждение break [4.1]. Специальный дополнительный вариант default будет выполнен в том случае, когда не будет найдено ни одного совпадения. Операторы if и switch той или иной синтаксической конструкции существуют практически во всех языках программирования (в первую очередь языках высокого уровня), и их часто называют операторами ветвления. 4.5. Условный оператор?В отличие от других операторов языка С, которые могут быть унарными или бинарными, специфический оператор условия является тернарным оператором. Это означает, что у него может быть три операнда [4.1]. Общий формат записи оператора условия: условие? выражение_1 : выражение_2 Если в результате вычисления условия будет получено значение TRUE (истина, не нуль), то выполняется выражение_1, и результатом выполнения оператора условия будет значение, полученное при вычислении этого выражения. Если в результате вычисления условия будет получено значение FALSE (ложь, т. е. нуль), то выполняется выражение_2, и результатом выполнения оператора условия будет значение, полученное при вычислении выражение_2. Оператор условия часто описывают как оператор?. Тернарный оператор условия? наиболее часто используется для присвоения переменной одного из двух значений в зависимости от некоторого условия. 4.6. Оператор break (от английского – прерывать)Оператор или утверждение break служит для немедленного выхода из цикла, будь то while, for или do–while. После выхода из цикла выполнение программы продолжается с утверждения (фрагмента программы), непосредственно следующего за циклом. Если оператор break встречается во вложенном цикле (вложенных циклах), то будет прекращено выполнение того цикла, в котором этот оператор встретился. Необходимость в использовании оператора прерывания break в теле цикла возникает тогда, когда условие продолжения итераций нужно проверять не в начале цикла (как в циклах while и for) и не в конце тела цикла (как в цикле do–while), а в середине тела цикла [4.2]. Формат записи оператора break: break; 4.7. Оператор continue (от английского – продолжать)Оператор или утверждение continue служит для перехода к следующей итерации цикла [4.2]. Оператор continue противоположен по действию оператору break. Оператор continue позволяет в любой точке тела цикла (while, for или do–while) прервать текущую итерацию и перейти к проверке условий продолжения цикла. В соответствии с результатами проверки либо заканчивается выполнение цикла, либо начинается новая итерация. При этом все утверждения (фрагменты программы), которые следуют за оператором continue (ключевым словом), автоматически пропускаются. Формат записи оператора continue: continue; 4.8. Оператор gotoСейчас во многих языках программирования оператор безусловного перехода типа goto не используется. Однако в языке программирования С он имеет место. Применение оператора goto не является хорошим стилем программирования. Но в некоторых случаях его применение бывает уместно. Иногда, при умелом использовании, оператор goto может оказаться весьма полезным, например, если нужно покинуть глубоко вложенные циклы [4.2]. Для оператора goto всегда необходима метка. Метка – это идентификатор с последующим двоеточием. Метка должна находиться в той же функции, что и оператор goto, переход в другую функцию невозможен. Общий формат записи оператора goto: goto метка; . . . метка: заданные действия. Метка может находиться как до, так и после оператора goto. С помощью оператора goto можно не только выходить из цикла, но и организовать цикл. Логические операторы отношения приведены в табл. 4.1.
Ниже приведены операции отношений в убывающей последовательности приоритетов [4.2]: Наивысший! > >= < <= == != && Низший || Как и в арифметических выражениях, для изменения порядка выполнения операций сравнения и логических операций можно использовать круглые скобки. Результат любой операции сравнения или логической операции есть 0 (нуль) или 1. Практическая частьПример 1. Напишите программу решения квадратного уравнения с проверкой на наличие вещественных (не комплексных) корней на основе только операторов if. Вид квадратного уравнения:
Как известно, квадратное уравнение будет иметь вещественные корни, если его дискриминант будет неотрицательным, т. е. когда
Программный код решения примера: #include <stdio. h> #include <conio. h> #include <math. h> int main(void) { float a, b, c; float D, x1, x2, x; printf("\n\t Equation a*x^2 + b*x + c = 0\n"); printf("\n\t Enter the coefficient a: "); scanf_s("%f", &a); printf("\t Enter the coefficient b: "); scanf_s("%f", &b); printf("\t Enter the coefficient c: "); scanf_s("%f", &c); D = b*b - 4*a*c; if (D >= 0 && a!= 0) { x1 = - b/(2*a) + (float)sqrt(D)/(2*a); x2 = - b/(2*a) - (float)sqrt(D)/(2*a); printf("\n\t The roots of the equation:\n\t x1 = %1.4f, x2 = %1.4f\n", x1, x2); } if (D < 0) printf("\n\t The roots of complex\n"); if (a == 0 && b!= 0) { x = - c/b; printf("\n\t As a = %1.0f,\n\t the solution of the equation is: %1.4f\n", a, x); } printf("\n Press any key: "); _getch(); return 0; } Возможный результат выполнения программы показан на рис. 4.1.
В программе последовательно проверяются условия с помощью операторов if. В последнем случае, когда коэффициент а = 0, квадратное уравнение вырождается и превращается в линейное уравнение. Решение в этом случае очевидно. В программе применены функции scanf_s() вместо стандартной функции scanf() языка С. Это сделано для того, чтобы по этим функциям не было предупреждений (warning) в MS Visual Studio 2008. Кроме того, в программу подключена библиотека math. h для действий с математическими функциями, например, sqrt(). В первом операторе if применено логическое условие И (&&) для проверки того, что дискриминант не равен отрицательному значению и одновременно чтобы первый коэффициент квадратного уравнения не был равен нулю. Аналогичное условие прописано и для последнего оператора if. Задание 1 Объясните включение float перед функцией sqrt(). В программу введите изменения для вычисления комплексных корней квадратного уравнения без подключения дополнительных библиотек. В программе предусмотрите ситуацию, когда все коэффициенты квадратного уравнения равны нулю. В программе предусмотрите подстановки найденных корней в заданное квадратное уравнение с выводом возможной невязки. Предусмотрите также меры по уменьшению невязки. Предусмотрите циклический ввод коэффициентов квадратного уравнения и вывода решения троекратно. Предусмотрите проверку всех возможных условий задания коэффициентов квадратного уравнения.Пример 2. Напишите программу решения квадратного уравнения с проверкой на наличие вещественных корней на основе конструкции if–else. Вид квадратного уравнения:
Программный код решения примера: #include <stdio. h> #include <conio. h> #include <math. h> int main(void) { float a, b, c; float D, x1, x2, x; printf("\n\t Equation a*x^2 + b*x + c = 0\n"); printf("\n\t Enter the coefficient a: "); scanf_s("%f", &a); printf("\t Enter the coefficient b: "); scanf_s("%f", &b); printf("\t Enter the coefficient c: "); scanf_s("%f", &c); D = b*b - 4*a*c; if (D >= 0 && a!= 0 && b!= 0) { x1 = - b/(2*a) + (float)sqrt(D)/(2*a); x2 = - b/(2*a) - (float)sqrt(D)/(2*a); printf("\n\t The roots of the equation:\n\t x1 = %1.4f, x2 = %1.4f\n", x1, x2); } else { if (a == 0 && b!= 0) { x = - c/b; if (c!= 0) printf("\n\t As a = %1.0f,\n\t the solution of the equation is: %1.4f\n", a, x); else printf("\n\t As a = %1.0f and c = %1.0f,\n\t the solution of the equation is: %1.0f\n", a, - x); } if (D < 0) printf("\n\t The roots of complex\n"); } printf("\n Press any key: "); _getch(); return 0; } В программе использованы вложенные операторы if. Результат выполнения программы при исключительной ситуации показан на рис. 4.2.
Задание 2 Добавьте в программу преобразование типов при использовании функции sqrt(). В программе предусмотрите вычисление комплексных и мнимых корней квадратного уравнения. Предусмотрите ввод целочисленных коэффициентов квадратного уравнения, используя тип int. В отчет вставьте возможный результат выполнения программы. Напишите программу ввода вещественных чисел и вывода абсолютного значения этого числа на основе конструкции if–else.Пример 3. Напишите программу классификации введенного с терминала символа на основе конструкции if–else if–else [4.1]. Символы (одиночные) будем считать как строчные и прописные буквы латинского алфавита, цифры от 0 до 9, и специальные символы. Программный код решения примера: #include <stdio. h> #include <conio. h> int main(void) { char c; // Ввод одиночного символа printf("\n\t Enter a single character: "); scanf_s("%c", &c); if ( c >= 'a' && c <= 'z' ) printf("\n\t This is a small letter\n"); else if ( c >= 'A' && c <= 'Z') printf("\n\t This is a capital letter\n"); else if ( c >= '0' && c <= '9') printf("\n\t This figure (digit)\n"); else printf("\n\t This is a special character\n"); printf("\n Press any key: "); _getch(); return 0; } Результат выполнения программы показан на рис. 4.3.
Задание 3 Сформируйте одно условие ввода букв как прописных, так и строчных с последующим выводом: This is an alphabetic character. Напишите программу циклического ввода символов и вывода результата их классификации до момента нажатия цифры 10*Х, где Х – номер компьютера, за которым выполняется лабораторная работа. Напишите программу решения квадратного уравнения с применением конструкций if–else if–else.Пример 4. Напишите программу расчета простого арифметического выражения на основе оператора switch. Программный код решения примера: #include <stdio. h> #include <conio. h> int main (void) { float value1, value2; char operat; printf("\n\t Printed on the keyboard expression: "); scanf_s("%f%c%f", &value1, &operat, sizeof(char), &value2); switch (operat) { case '+': printf("\n\t Result: %1.4f\n", value1 + value2); break; case '-': printf("\n\t Result: %1.4f\n", value1 - value2); break; case '*': printf("\n\t Result: %1.4f\n", value1 * value2); break; case '/': if (value2 == 0.0) printf("\n\t Division by zero.\n"); else printf("\n\t Result: %1.4f\n", value1 / value2); break; default: printf("\n\t Unknown arithmetic operator\n\t error or enter a number. Break!\n"); break; } // End switch printf("\n Press any key: "); _getch(); return 0; } В программе использована полная форма оператора switch. Оператор break инициирует немедленный выход из оператора switch. Возможно использование вложенных операторов switch. Возможный результат выполнения программы показан на рис. 4.4.
Задание 4 Проверьте деление числа на нуль и ввода недопустимого символа. Примените условие равенства нулю вводимого числа без знака "==". Напишите программу расчета простого выражения с помощью конструкций if–else if–else. Напишите программу деления суток на "Morning" (утро), "Day" (день), "Afternoon" (послеобеденное время), "Evening" (вечер), "Night" (ночь). Время ввода задается пользователем с клавиатуры.Пример 5. Напишите программу вычисления двух целых случайных чисел и определения наибольшего из них. Определение наибольшего числа произведите с помощью оператора условия? Программный код решения примера: #include <stdio. h> #include <conio. h> #include <stdlib. h> // Для функций случайных чисел #include <time. h> int main (void) { int a, b, maxab; unsigned int some; long int L; L = (long) time(NULL); some = (unsigned) L/2; srand(some); a = rand(); b = rand(); printf("\n\t Random numbers: a = %d; b = %d\n", a, b); // Оператор условия для определения максимального числа maxab = (a > b) ? a : b; printf("\n\t Maximum number: %d\n", maxab); printf("\n Press any key: "); _getch(); return 0; } В программе использованы функции генерации псевдослучайных чисел rand() и задания исходного псевдослучайного числа srand(). Указанные функции входят в стандартную библиотечную функцию stdlib. h. Функция time() входит в библиотечную функцию time. h, которая поддерживает функции, обращающиеся к системному времени. Для переменных L и some выполнено приведение типов. При каждом обращении к функции rand() возвращается целое в интервале между нулем и значением RAND_MAX, которое в любой реализации должно быть не меньше числа[4.2] Возможный результат выполнения программы показан на рис. 4.5.
Задание 5 При выводе максимального числа предусмотрите сообщение об имени числа, т. е. a или b. В цикле сформируйте вектор десяти случайных чисел из интервала [0;1] и выведите на дисплей. Для задания системного времени примените директиву define N X, где Х – номер компьютера, за которым выполняется лабораторная работа. Примените оператор условия? для определения абсолютного значения вещественного числа, которое должно вводиться пользователем с клавиатуры.Пример 6. Используя оператор условия? и переключатель switch напишите программу определения времени года по вводимым числам от 1 до 12, считая, что цифра 1 соответствует январю, цифра 2 – февралю и т. д. Программный код решения примера: #include <stdio. h> #include <conio. h> int main (void) { int x; printf("\n\t Enter a whole number between 1 and 12: "); scanf_s("%d", &x); switch (x > 0 && x < 3 ? 1 : x == 12 ? 1 : x > 2 && x < 6 ? 2 : x > 5 && x < 9 ? 3 : x > 8 && x < 12 ? 4 : x > 12 || x < 1 ? 5 : 5) { case 1 : printf("\n\t This Winter\n"); break; case 2 : printf("\n\t This Spring\n"); break; case 3 : printf("\n\t This Summer\n"); break; case 4 : printf("\n\t This Autumn\n"); break; case 5 : printf("\n\t This is a mistake (Error)\n"); } printf("\n Press any key: "); _getch(); return 0; } В программе три месяца зимы кодируются цифрой 1, три месяца весны –цифрой 2, три месяца лета – цифрой 3, три месяца осени – цифрой 4. Если введенная цифра не входит в целочисленный интервал [1; 12], то эта ситуация кодируется цифрой 5. Возможный результат выполнения программы показан на рис. 4.6.
Задание 6 Проверьте программу по вводимым вещественным числам. Объясните результат. Проверьте программу по вводимым буквам или знаков, имеющихся на клавиатуре. Объясните результат. В программе вместо 5-го пункта, т. е. вместо case 5, примените операцию default. Напишите программу определения времени года по вводимым числам без операторов условия?.Пример 7. Напишите программу распечатки четных целых чисел от 0 до 30. #include <stdio. h> #include <conio. h> int main (void) { int x; printf("\n\t Even numbers from 0 to 30:\n\n"); for (x = 0; x < 31; x++) { if ( x % 2 ) continue; printf("\t\t %3d\n", x); } printf("\n Press any key: "); _getch(); return 0; } В программе в качестве проверки условия использовано деление по модулю (х%2). Если остаток от деления числа х не равен нулю, то утверждение (оператор, инструкция) continue передает управление непосредственно инструкции, проверяющей условное выражение, после чего циклический процесс продолжается. С помощью программы выводятся только четные числа, а при обнаружении нечетного числа происходит преждевременный переход к следующей итерации цикла, и функция printf() опускается. Функция printf() включена в тело цикла оператора for. Результат выполнения программы показан на рис. 4.7.
Задание 7 В программу включите действия подсчета суммы четных чисел. Подсчитайте число итераций оператора цикла. В программе вместо цикла for примените цикл while. Объясните действие инструкции continue. В программе вместо цикла for примените цикл do–while. Объясните действие инструкции continue. Напишите программу вывода четных чисел без оператора continue. Подсчитайте число итераций оператора цикла. Сделайте вывод четных чисел из интервала от Х до 10*Х, где Х – номер компьютера, за которым выполняется лабораторная работа. Подсчитайте сумму четных чисел. Измените условие для вывода четных чисел.Пример 8. Напишите программу подсчета суммы трех чисел из трех вложенных циклов и, если сумма делится без остатка на число 3, то прекратите сравнение чисел и выйдите из циклов с последующей распечаткой этой суммы и слагаемых. Первый цикл начинается с 51 до 1, второй цикл – с 41 до 1, третий цикл – с 1 до 50. Первый цикл изменяется через 7 единиц, второй цикл изменяется на 1, третий цикл изменяется на 7 единиц. Программный код решения примера с оператором goto: #include <stdio. h> #include <conio. h> int main (void) { int a, b, c, i, j, k; a = b = c = 0; for (i = 51; i >= 1; i -= 7 ) for (j = 41; j >= 1; --j ) for (k = 1; k <= 50; k += 7) if ( ((i + j + k) % 3 == 0) && (i < 51) && (j < 41)) {a = i; b = j; c = k; goto sum3; } sum3: printf("\n\t The sum (%d?+?%d?+?%d) is equal %d\n", a, b, c, a + b + c); printf("\n Press any key: "); _getch(); return 0; } В программе использовано декрементирование (уменьшение на единицу) переменной j в форме --j. Переменная i с каждой итерацией цикла уменьшается на 7 единиц. Переменная k с каждой итерацией цикла увеличивается на 7 единиц. После оператора if включены фигурные скобки для выполнения нескольких действий при выполнении заданного условия оператора if. Результат выполнения программы показан на рис. 4.8.
Примечание. Оператор goto нельзя применять для перехода в тело цикла, т. е. метка не должна быть внутри оператора цикла. Метка может появиться текстуально до или после оператора goto. Задание 8 В программу включите подсчет числа итераций каждого из циклов. Вместо оператора безусловного перехода goto примените оператор break. Произвести также подсчет числа итераций каждого из циклов. Сравните с аналогичными результатами предыдущего пункта задания. В программе вместо операторов for примените операторы while. Выполните действия двух предыдущих пунктов задания. Напишите программу ввода символа до тех пор, пока не будет введен заранее определенный символ, например 'w'. Используйте оператор goto. Предусмотрите отступ от левого края дисплея.Пример 9. Напишите программу распечатки на консоль простых чисел из диапазона от 2 до N, где N – число, вводимое пользователем с клавиатуры, которое не превосходит, например, 1000. Как известно, простое число – это целое положительное число больше единицы, которое не делится без остатка ни на одно другое целое положительное число, кроме единицы и самого себя. Единица не считается простым числом. Возможный программный код решения примера: #include <stdio. h> #include <conio. h> #include <stdlib. h> // для exit() #define Nmax 50 int main (void) { int i, j; int ok, in; int N; printf("\n Enter an integer from 2 to %d: ", Nmax); in = scanf_s("%d", &N); if (in == 0 || N < 2 || N > Nmax) { printf("\n Error input. Press any key: "); _getch(); exit(1); } printf("\n Prime numbers from 2 to %d:\n\n", N); for ( i = 2; i <= N; i++ ) { ok = 1; for ( j = 2; j < i; j++ ) if ( (i%j) == 0 ) ok = 0; if ( ok ) printf(" %3d", i); } printf("\n\n Press any key: "); _getch(); return 0; } В программе с помощью оператора if осуществляется проверка правильности ввода данных с клавиатуры. Кроме того, этим же оператором проверяется остаток от деления двух чисел и условной истинности, когда переменная ok не равна нулю. С помощью препроцессорной директивы define определяется верхняя допустимая граница для простых чисел. Пример выполнения программы показан на рис. 4.9.
Задание 9 В программе вместо оператора цикла for примените оператор while. В программе примите допустимое число Nmax, равное 9999. Предусмотрите при этом форматированный вывод на консоль простых чисел построчно, по 15 чисел в каждой строке. Используйте тернарный оператор ?:. Предыдущий пункт задания выполните с помощью операторов if, else.Контрольные вопросыКак организуются множественные действия в операторе условия if? Какой формат записи имеет тернарный оператор условия? Какой оператор условия рекомендуется использовать для программирования меню? В чем различие и сходство между операторами break и continue? Как можно обеспечить выход из вложенных циклов? Как можно организовать переходы в различные точки программы на С? Какие логические операторы отношения используются в языке С? Что произойдет, если в операторе switch после метки case не использовать оператор break? Что произойдет, если в операторе switch не поставить метку default и условие переключения не совпадет ни с одной меткой case? | ||||||||||||||
| ||||||||||||||
Программирование на языке C в Microsoft Visual Studio 2010 | ||||||||||||||
| ||||||||||||||
| ||||||||||||||
| ||||||||||||||
5. Лекция: Числовые массивы в языке программирования С: | ||||||||||||||
| ||||||||||||||
| ||||||||||||||
| ||||||||||||||
Теоретическая частьВ языке программирования С заложены средства для задания последовательностей упорядоченных данных [5.1]. Такие последовательности называются массивами. В массивах должны быть упорядочены данные одного и того же типа. В данной лабораторной работе будут рассматриваться массивы с целыми и вещественными типами данных, т. е. типы int, float или double. Массивы данных могут быть одномерными (векторами размера 1 5.1. Одномерные массивыОдномерный массив – это список связанных однотипных переменных. Общая форма записи одномерного массива [5.2]: тип имя_массива[размер]; В приведенной записи элемент тип объявляет базовый тип массива. Количество элементов, которые будут храниться в массиве с именем имя_массива, определяется элементом размер. В языке С индексация массива начинается с нуля. Например, если размер массива определен величиной 9, то в массиве можно хранить 10 элементов с индексацией 0, 1, 2, 3, 4, 5, 6, 7, 8, 9. Доступ к отдельному элементу массива осуществляется с помощью индекса. Индекс описывает позицию элемента внутри массива. Все массивы занимают смежные ячейки памяти, т. е. элементы массива в памяти расположены последовательно друг за другом. Ячейка памяти с наименьшим адресом относится к первому элементу массива, а с наибольшим – к последнему. Для одномерных массивов общий размер массива в байтах вычисляется по формуле: всего байт = размер типа в байтах * количество элементов В языке С нельзя присвоить один массив другому. Для передачи элементов одного массива другому необходимо выполнить присвоение поэлементно. 5.2. Двухмерные массивы, матрицыДвухмерный массив представляет собой список одномерных массивов. Общая форма записи двухмерного массива: тип имя_массива[размер1] [размер2]; В приведенной записи размер1 означает количество строк двухмерного массива, а размер2 – количество столбцов. В двухмерном массиве позиция любого элемента определяется двумя индексами. Индексы каждого из размеров массива начинаются с 0 (с нуля). Место хранения для всех элементов массива определяется во время компиляции. Память, выделенная для хранения массива, используется в течение всего времени существования массива. Для двухмерных массивов общий размер массива в байтах вычисляется по формуле: всего байт = число строк * число столбцов * размер типа в байтах 5.3. Многомерные массивыОбщая форма записи многомерного массива: тип имя_массива[размер1] [размер2]... [размерN]; Индексация каждого размера начинается с нуля. Элементы многомерного массива располагаются в памяти в порядке возрастания самого правого индекса. Поэтому правый индекс будет изменяться быстрее, чем левый (левые). При обращении к многомерным массивам компьютер много времени затрачивает на вычисление адреса, так как при этом приходится учитывать значение каждого индекса [5.2]. Следовательно, доступ к элементам многомерного массива происходит значительно медленнее, чем к элементам одномерного. В этой связи использование многомерных массивов встречается значительно реже, чем одномерных или двухмерных массивов. Для многомерных массивов общий размер многомерного массива в байтах вычисляется по формуле: всего байт = размер1* размер2*...* размерN *размер типа в байтах Очевидно, многомерные массивы способны занять большой объем памяти, а программа, которая их использует, может очень быстро столкнуться с проблемой нехватки памяти. Для определения размера типа в байтах применяется функция sizeof(), которая возвращает целое число. Например, sizeof(float). 5.4. Инициализация массивовВ языке С массивы при объявлении можно инициализировать [5.2]. Общая форма инициализации массива: тип имя_массива[размер1] * [размерN] = {список_значений}; В список_значений входят констант, разделенных запятыми. Типы констант должны быть совместимыми с типом массива. Пример инициализации одномерного массива: int A[5] = {1, 2, 3, 4, 5}; При этом A[0] = 1, A[1] = 2 и т. д. При инициализации многомерного массива для улучшения наглядности элементы инициализации каждого измерения можно заключать в фигурные скобки. Пример инициализации двухмерного массива: int MN[3][4] = { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} }; Массив MN[3][4] – это матрица, у которой 3 строки и 4 столбца. Для многомерных массивов инициализацию можно также проводить с указанием номера инициализируемого элемента. Пример инициализации трехмерного массива: int XYZ[2][3][4] = { { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} }, { {13, 14, 15, 16}, {17, 18, 19, 20}, {21, 22, 23, 24} } }; Как видно, массив XYZ содержит два блока, каждый из которых есть матрица размера 3 В языке С возможна инициализация безразмерных массивов. Например, для одномерного массива: int A[ ] = {1, 2, 3, 4, 5}; В многомерном массиве размер самого левого измерения также можно не указывать. В частности, для инициализации массива MN[3][4] допустима следующая запись: int MN[][4] = { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} }; При инициализации многомерных массивов необходимо указать все данные (размерности) за исключением крайней слева размерности. Это нужно для того, чтобы компилятор смог определить длину подмассивов, составляющих массив, и смог выделить необходимую память. Рассмотрим пример безразмерной инициализации для трехмерного массива целых чисел: int XYZ[][3][4] = { { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} }, { {13, 14, 15, 16}, {17, 18, 19, 20}, {21, 22, 23, 24} } }; Вывод трехмерного массива на консоль (дисплей) можно выполнить по следующей программе: #include <stdio. h> #include <conio. h> int main (void) { int i, j, k; int XYZ[][3][4] = { { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} }, // 1-й { {13, 14, 15, 16}, {17, 18, 19, 20}, {21, 22, 23, 24} } }; // 2-й for (i = 0; i < 2; ++i) { printf("\n"); for (j = 0; j < 3; ++j) { printf("\n"); for (k = 0; k < 4; ++k) printf(" %3d", XYZ[i][j][k]); } } printf("\n\n Press any key: "); _getch(); return 0; } Практическая частьПример 1. Напишите программу заполнения одномерного массива случайными числами из интервала от 1 до 15 по случайному равномерному закону. Отсортировать массив случайных чисел по возрастанию. Для решения поставленной задачи применим сортировку методом прямого выбора [5.3]. Алгоритм сортировки заключается в следующем: В исходной последовательности из N элементов отыскивается элемент с наименьшим ключом. Он меняется местами с первым элементом. В оставшейся последовательности из (N–1) элементов отыскивается минимальный элемент и меняется местами со вторым элементом и т. д., пока не останется один, самый большой элемент.Программный код решения примера: #include <stdio. h> #include <conio. h> #include <time. h> #include <stdlib. h> #define Left 1 #define Right 15 #define N 10 int main (void) { float R, r, min; float A[N]; int i, j, k; unsigned int some; long int L; L = (long) time(NULL); // Системное время some = (unsigned) L; // Приведение типов srand(some); // Задание исходного случайного числа для rand() printf("\n\t The initial array of random numbers in the interval [%d, %2d]\n", Left, Right); for (i = 0; i < N; ++i) {// Случайное число из интервала [0,1] r = (float) rand()/RAND_MAX; // Формирование случайного числа из заданного интервала R = Left + (Right - Left) * r; // Заполнение массива случайными числами A[i] = R; } // Печать элементов исходного массива for (i = 0; i < N; ++i) printf("\n\t %5d) %10.4f", i + 1, A[i]); // Сортировка методом выбора for (i = 0; i < (N - 1); ++i) { min = A[i]; k = i; for (j = i + 1; j < N; ++j) if (A[j] < min) { k = j; min = A[k]; } A[k] = A[i]; A[i] = min; } // Печать отсортированного массива по возрастанию printf("\n\n\t Sort an array:\n"); for (i = 0; i < N; ++i) printf("\n\t %5d) %10.4f", i + 1, A[i]); printf("\n\n Press any key: "); _getch(); return 0; } Возможный результат выполнения программы показан на рис. 5.1.
В программе использованы директивы препроцессора для задания левой границы (#define Left 1), правой границы (#define Right 15) и размера одномерного массива (#define N 10). Включены дополнительные библиотеки time. h – для обращения к функциям системного времени, stdlib. h – для обращения к функциям генерации псевдослучайных чисел. Задание 1 Границы формирования случайных чисел и размерность массива задайте без препроцессорных директив. Произведите сортировку массива случайных чисел по убыванию. Введите границы интервала формирования случайных чисел по правилу: Left = X, Right = 3*X, где Х – номер компьютера, за которым выполняется лабораторная работа. Произвести сортировку по убыванию. Найдите и изучите сортировку методом прямого обмена (сортировка методом пузырька). Напишите программу формирования одномерного массива случайными числами и их сортировки методом пузырька.Пример 2. Напишите программу поиска максимального элемента в заданном одномерном массиве. Элементы массива являются целыми числами. Программный код решения примера: #include <stdio. h> #include <conio. h> int main (void){ int i, size, max; int A[ ] = {3, 5, 2, 8, 12, 0, -7, -3, -21}; size = sizeof(A)/sizeof(A[0]); printf("\n\t The dimention of the array A is equal to: %d\n", size); max = A[0]; // Предполагаемый максимум for (i = 0; i < size; ++i) if (A[i] > max) max = A[i]; printf("\n\t Maximum array element: %d\n", max); printf("\n\n Press any key: "); _getch(); return 0; } В программе использована инициализация безразмерного массива и определения его размерности с помощью функции sizeof(). Результат выполнения программы показан на рис. 5.2.
Задание 2 В программе предусмотрите вывод на консоль значений массива в виде строки. Определите индекс максимального элемента. Определите минимальный элемент массива и его индекс. Напишите программу перемещения максимального и минимального элементов на края массива. Например, максимальный элемент поместить в конец массива, а минимальный – в начало массива. Выведите на консоль исходный массив данных и преобразованный массив в виде двух строк, следующих друг за другом. Напишите программу ввода целых чисел массива с клавиатуры и определения максимального и минимального элементов в нем. Количество вводимых чисел задается по соотношению 5*Х, где Х – номер компьютера, за которым выполняется лабораторная работа.Пример 3. Напишите программу циклической перестановки чисел заданного массива так, чтобы i-e число стало (i+1)-м, а последнее число – первым. Выведите на дисплей исходный массив и преобразованный. Программный код решения примера: #include <stdio. h> #include <conio. h> #define N 55 int main (void) { int i, j, k; double D[ ] = {1.23, 2.34, 3.45, 4.56, 5.67, 6.78}; double B[N]; // Заведомо больший размер, чем у массива D // Обнуление массива и выделение памяти для него for (i = 0; i < N; ++i) B[i] = 0.0; k = sizeof(D)/sizeof(D[0]); B[0] = D[k-1]; for (i = 0; i < (k - 1); ++i) B[i+1] = D[i]; printf("\n\t The original array:\n"); for (i = 0; i < k; ++i) printf("%8.2f", D[i]); printf("\n\n\t The reconfigured array:\n"); for (j = 0; j < k; ++j) printf("%8.2f", B[j]); printf("\n\n Press any key: "); _getch(); return 0; } Результат выполнения программы показан на рис. 5.3.
Задание 3 Выведите на дисплей (для проверки) два элемента массива В, номера которых превышают размерность массива D. Программным путем подсчитайте сумму элементов массива и выведите ее на консоль. Дополните программу формированием массива, в котором элементы расположены в обратном порядке, чем в исходном массиве. Сформируйте массив кодов строчных букв латинского алфавита. Выведите на дисплей сами буквы и их коды, которые являются элементами массива.Пример 4. В данном одномерном массиве вещественных чисел поменяйте местами элементы, стоящие на нечетных местах, с элементами, стоящими на четных местах. Предусмотрите четность и нечетность размерности массива. Для определения четности места в заданном массиве можно использовать операцию деления по модулю, т. е. %. Программный код решения примера: #include <stdio. h> #include <conio. h> // Размер массива #define n 7 int main (void) { int i, k; // Пример массива float A[n] = {1.23F, 2.34F, 3.45F, 4.56F, 5.67F, 6.78F, 7.89F}; float B[n];// Вспомогательный массив // Обнуление массива for (i = 0; i < n; ++i) B[i] = 0; // Распечатка заданного массива printf("\n\t\t The original array of dimention n = %d:\n", n); printf("\t"); for (i = 0; i < n; ++i) printf("%6.2f", A[i]); // Распечатка преобразованного массива printf("\n\n\t\t The reconfigured array:\n"); for (i = 0; i < n; ++i) { k = i % 2; // Для определения четности индекса массива if (k == 0 && i < n - 1 ) B[i] = A[i + 1]; else if (k!= 0 && i > 0 ) B[i] = A[i-1]; else if (k == 0 && i < n) B[i] = A[i]; } printf("\t"); for (i = 0; i < n; ++i) printf("%6.2f", B[i]); printf("\n\n Press any key: "); _getch(); return 0; } При инициализации массива каждый его элемент снабжен суффиксом F. Результат выполнения программы показан на рис. 5.4.
Задание 4 Вывод на дисплей пользователя исходного массива и преобразованного выполните без дополнительного массива. В программу внесите изменения, чтобы не использовать целочисленную переменную для определения остатка от деления Проверьте программу для массива с четным количеством элементов. Напишите программу без использования операции определения остатка от деления, т. е. без использования операции i % 2. Напишите программу для двухмерного массива вещественных чисел. Смену мест элементов (четных с нечетными) предусмотрите в каждой строке матрицы. Размер матрицы примите равным nУказание: Можно предусмотреть определение остатка от деления, как индекса строки, так и индекса столбца и суммы индексов строки и столбца. Пример 5. Напишите программу заполнения квадратной матрицы (заданного размера n > 2) по спирали натуральными числами начиная с левого верхнего угла (принимая его за номер 1) и двигаясь по часовой стрелке. Образец заполнения:
Программный код решения примера: #include <stdio. h> #include <conio. h> #define n 13 int main(void) { int i = 1, j, k; int p = n/2; int A[n][n]; // Обнуление матрицы for (j = 0; j < n; ++j) for (k = 0; k < n; ++k) A[j][k] = 0; printf("\n\t Spiral matrix of dimention (%d x %d):\n", n, n); for (k = 1; k <= p; k++) // Число спиралей { // Верхний горизонтальный столбец for (j = (k-1); j < (n-k+1); j++) A[(k-1)][j] = i++; // Правый верхний столбец for (j = k; j < (n-k+1); j++) A[j][n-k] = i++; // Нижний горизонтальный столбец for (j = (n-k-1); j >= (k-1); --j) A[n-k][j] = i++; // Левый верхний столбец for (j = (n-k-1); j >= k; j--) A[j][(k-1)] = i++; } if ( n % 2 ) A[p][p] = n*n; // Распечатка матрицы for (i = 0; i < n; ++i) for (j = 0; j < n; ++j) { printf("%5d", A[i][j]); if (j == (n-1)) printf("\n"); } printf("\n Press any key: "); _getch(); return 0; } Результат выполнения программы показан на рис. 5.5.
Задание 5 Дополните программу расчетом количества четных элементов выше главной диагонали матрицы и количества нечетных элементов ниже главной диагонали, не включая саму диагональ. Протестировать программу при изменении размера матрицы от 3 до 12. Результаты вставьте в отчет лабораторной работы. Напишите программу заполнения матрицы по спирали против часовой стрелки, начиная с верхнего левого угла (номер 1). Напишите программу с вводом направления заполнения матрицы. Например, если будет введено число 1, то заполнение выполните по часовой стрелке, если введено число –1, – против часовой стрелки. Напишите программу заполнения прямоугольной матрицы натуральными числами "змейкой" – построчно: слева – направо – справа – налево и т. д.Пример 6. Каждый день производятся замеры некоторых величин (вещественных значений), причем значения этих величин сводятся в прямоугольную таблицу размера n Этот пример относится к определению трехмерного массива данных. Программный код решения примера: #include <stdio. h> #include <conio. h> #include <time. h> #include <stdlib. h> #define n 6 #define m 7 #define N 30 const int Left = -12; // Левая граница const int Right = 21; // Правая граница int main (void) { float R, r; float A[N][n][m]; int i, j, k; // Инициализация генератора случайных чисел srand((unsigned) time(NULL)); printf("\n\t The values of every 10 days from 30 days:"); // Формирование данных за 30 дней for (k = 0; k < N; ++k) for (i = 0; i < n; ++i) for (j = 0; j < m; ++j) { r = (float) rand()/RAND_MAX; R = Left + (Right - Left)*r; A[k][i][j] = R; } // Печать данных за каждый 10-й день for (k = 0; k < N; k += 10) { printf("\n"); for (i = 0; i < n; ++i) { printf("\n"); for (j = 0; j < m; ++j) printf("%10.4f", A[k][i][j]); } } printf("\n Press any key: "); _getch(); return 0; } В программе используется трехмерный массив размера 30 Возможный результат выполнения программы показан на рис. 5.6.
Задание 6 В программе сделайте предварительное обнуление трехмерного массива. Измените тип вещественных данных float на тип double. Проверьте работу программы. Найдите максимальный и минимальный элементы массивов. Найдите абсолютные значения максимального и минимального элементов всего трехмерного массива. Выполните заполнение трехмерного массива размера 3Пример 7. Напишите программу по перемножению двух матриц А и В с размерностями (m Условием перемножения двух матриц А и В является равенство числа столбцов матрицы А и числа строк матрицы В. Если первая матрица А имеет размер m
Поэлементное перемножение двух матриц в стандартной математической форме имеет следующий вид:
С учетом синтаксиса формирования массивов в языке С индексация должна начинаться с нуля, поэтому формулу перепишем в следующем виде:
Программный код решения примера: #include <stdio. h> #include <conio. h> #define m 4 #define r 5 #define n 3 int main (void) { int i, j, k; // переменные циклов const int A[m][r] = {{1,2,3,4,5}, {2,3,4,5,6}, {2,2,2,2,2}, {3,3,3,3,3}}; const int B[r][n] = {{9,8,7}, {1,2,3}, {4,5,6}, {7,8,9}, {1,1,1}}; // Массив под результат произведения двух матриц int C[m][n]; // Обнуление результирующей матрицы for (i = 0; i < m; i++) for (j = 0; j < n; j++) C[i][j] = 0; // Формирование результата произведения двух матриц for (i = 0; i < m; i++) for (j = 0; j < n; j++) for (k = 0; k < r; k++) C[i][j] = C[i][j] + A[i][k]*B[k][j]; // Распечатка результата произведения двух матриц printf("\n 1) Index: \"ijk\". Matrix (%dx%d):\n", m, n); for (i = 0; i < m; i++) { printf("\n"); for (j = 0; j < n; j++) printf(" %4d", C[i][j]); } printf("\n\n... Press any key: "); _getch(); return 0; } В программе используются три цикла по формированию произведения двух матриц. Первый цикл (переменная i) связан с количеством строк первой матрицы (матрицы А), второй цикл (переменная j) связан с количеством столбцов второй матрицы (матрица В), третий цикл (переменная k) связан со смежной размерностью матриц, которая исчезает в результирующей матрице С. Матрицы А и В определены как неизменяемы типы (const int). Приведенный программный метод можно назвать как первый метод, метод "ijk". Результат выполнения программы показан на рис. 5.7.
Задание 7 Измените программу, чтобы циклы были организованы в порядке "jki". Это можно назвать вторым методом программного перемножения двух матриц. Измените программу, чтобы циклы были организованы в порядке "kji". Это можно назвать третьим методом программного перемножения двух матриц. Примените тип float для заданных матриц. Предусмотрите форматированный вывод результата на консоль.Контрольные вопросыКак организуются многомерные числовые массивы в языке С? Как организуется индексирование числовых массивов в языке С? На кого или на что возлагается контроль границ числовых массивов в языке программирования С? В какой очередности и как происходит заполнение многомерных числовых массивов в программах на языке С? Для чего применяется начальная инициализация числовых массивов при дальнейшем их использовании? Сколько потребуется операторов цикла для вывода на консоль двухмерного числового массива (матрицы чисел)? Почему при определении размерности массива с помощью препроцессорной директивы define не используется точка с запятой после числового значения? | ||||||||||||||
|
Программирование на языке C в Microsoft Visual Studio 2010 |
|
|
|
6. Лекция: Символьные массивы в языке С. Работа со строками: В лекции надлежит изучить задание и инициализацию символьных массивов в языке программирования С, изучить решение задач с символьными массивами, изучить базовые функции для работы со строками. |
|
|
|
Теоретическая частьВ языке программирования С заложены средства для задания последовательностей упорядоченных данных [6.1]. Такие последовательности называются массивами. В массивах должны быть упорядочены данные одного и того же типа. В данной лабораторной работе будут рассматриваться массивы символов, которые определятся типом char. Одномерный массив наиболее часто применяется в виде строки символов. Строка – это одномерный массив символов, заканчивающийся нулевым символом [1; 2]. В языке С признаком окончания строки служит символ '\0'. При объявлении массива символов, предназначенного для хранения строки, необходимо отвести одно место для нуля, т. е. для символа окончания строки '\0'. Например, если дана строка qwerty, в которой 6 символов, каждый из которых занимает в памяти 1 байт, то при инициализации такой строки необходимо отвести 1 байт для нулевого символа. Поэтому следует сделать объявление строки для семи символов: char str[7] = "qwerty"; Альтернативным объявлением может служить безразмерная инициализация: char str[ ] = "qwerty"; При этом в случае определения длины строки результатом будет число 6. Размер строки не изменится, если в ней указать символ окончания строки: char str[ ] = "qwerty\0"; Аналогично числовым массивам в языке С могут использоваться массивы строк, т. е. набор одномерных массивов символов. Например, сервер базы данных сверяет команды пользователей с массивом допустимых команд [6.2]. В качестве массива строк для этого случая будет служить двухмерный символьный массив. Размер левого измерения определяет количество строк, а правого – максимальную длину каждой строки [6.2]. Например: char str[30][80]; Число 30 – это количество строк массива, а число 80 – максимальная длина каждой строки с учетом нулевого символа завершения строки. Чтобы обратиться к отдельной строке двухмерного символьного массива, достаточно указать только левый индекс объявленного массива. Многомерные символьные массивы образуются, как и числовые массивы: char str[n][m]...[N]; В объявлении массива n – первая размерность, m – вторая размерность, ..., N – N-я (последняя) размерность. Значения размерностей – целые неотрицательные числа. 6.1. Одномерные символьные массивы – строкиОдномерный массив – это список связанных однотипных переменных. Общая форма записи одномерного массива [6.2]: тип имя_массива[размер]; В приведенной записи элемент тип объявляет базовый тип массива. Количество элементов, которые будут храниться в массиве с именем имя_массива, определяется элементом размер. В языке С индексация массива (символьного или числового) начинается с нуля. Доступ к отдельному элементу массива осуществляется с помощью индекса. Индекс описывает позицию элемента внутри массива. Все массивы занимают смежные ячейки памяти, т. е. элементы массива в памяти расположены последовательно друг за другом. Ячейка памяти с наименьшим адресом относится к первому элементу массива, а с наибольшим – к последнему. Для массива символов при инициализации массива необходимо резервировать место для символа окончания строки, т. е. для символа '\0'. Символьная константа – это набор символов, заключенных в двойные апострофы, например, "hello". В конец символьной строки не требуется обязательно добавлять нуль, компилятор языка С делает это автоматически. При инициализации символьной строки как одномерного массива необходимо предусмотреть место для нулевого символа, например: char str[7] = "hello"; Каждая строка содержит на один символ больше, чем задано явно: все строки оканчиваются нулевым символом, имеющим значение 0. Для одномерных массивов общий размер массива в байтах вычисляется по формуле: всего байт = размер типа в байтах * количество элементов 6.2. Двухмерные символьные массивыДвухмерный массив представляет собой список одномерных массивов. Общая форма записи двухмерного массива: тип имя_массива[размер1] [размер2]; В приведенной записи размер1 означает число строк двухмерного массива, а размер2 – количество столбцов. При этом размерность размер2 определяет максимальную длину для заданного массива. Обычно размер2 задают с некоторым запасом. В двухмерном массиве позиция любого элемента определяется двумя индексами. Индексы каждого из размеров массива начинаются с нуля. Место хранения для всех элементов массива определяется во время компиляции. Память, выделенная для хранения массива, используется в течение всего времени существования массива. Для двухмерных массивов заданного типа общий размер массива в байтах вычисляется по формуле: всего байт = количество строк * количество столбцов * размер в байтах Инициализация двухмерного символьного массива может быть определена либо посимвольно, либо построчно, например char str[3][80] = { {'1','2','3','4','5'}, {'x','y','z'}, {'A','B','C','D'} }; char str2[3][80] = { "", "x_y_z", "A B C D" }; Число 80 взято с запасом для возможной длины строки. Число 3 – это количество строк двухмерного массива. В обоих случаях могут быть добавлены символы окончания строки ('\0'). Символ '\0' не выводится на экран дисплея и не передается в файл, например, в текстовый файл. В то же время необходимо помнить, что каждая строка заканчивается нулевым символом. 6.3. Многомерные символьные массивыОбщая форма записи многомерного массива: тип имя_массива[размер1] [размер2]... [размерN]; Индексация каждого размера начинается с нуля. Элементы многомерного массива располагаются в памяти в порядке возрастания самого правого индекса. Поэтому правый индекс будет изменяться быстрее, чем левый (левые). При этом в конце каждой строки подразумевается нулевой символ. При обращении к многомерным массивам компьютер много времени затрачивает на вычисление адреса, так как при этом приходится учитывать значение каждого индекса [6.2]. Поэтому доступ к элементам многомерного массива происходит значительно медленнее, чем к элементам одномерного. В связи с этим использование многомерных массивов встречается значительно реже, чем одномерных или двухмерных массивов. Для многомерных массивов общий размер многомерного массива в байтах вычисляется по формуле: всего байт = размер1 * размер2* ... *размерN * размер типа в байтах Очевидно, многомерные массивы способны занять большой объем памяти, а программа, которая их использует, может очень быстро столкнуться с проблемой нехватки памяти. Для определения размера типа в байтах применяется функция sizeof(), которая возвращает целое число. Например, sizeof(char). При инициализации многомерных массивов необходимо указать все данные (размерности) за исключением крайней слева размерности. Это нужно для того, чтобы компилятор смог определить длину подмассивов, составляющих массив, и смог выделить необходимую память. Практическая частьПример 1. Напишите программу определения длины заданных строк и их распечатки, а также определения размера строк в байтах. Для решения поставленной задачи применим библиотечную функцию strlen() и оператор sizeof. Программный код решения примера: #include <stdio. h> #include <conio. h> #include <string. h> int main (void) { char str[] = {'A','B','C','D','\0'}; char str2[] = "hello, world\0"; printf("\n\t The lines are:\n\n\t "); puts(str); printf("\t "); // Для отступа от края дисплея puts(str2); printf("\n\t The length of the 1st line (%s) is: %i\n", str, strlen(str)); printf("\t The size of the memory of the 1st line (%s) is: %i\n", str, sizeof str); printf("\n\t The length of 2-nd line (%s) is: %i\n", str2, strlen(str2)); printf("\t Memory size 2-nd line (%s) is: %i\n", str2, sizeof str2); printf("\n Press any key: "); _getch(); return 0; } В программе функция strlen() возвращает длину строки, причем строка должна заканчиваться символом строки [6.2]. Символ конца строки не учитывается. Для работы с функцией strlen() необходимо подключить заголовок <string. h>. Оператор sizeof во время компиляции программы получает размер типа или значения. Для определения размера типа оператор используется со скобками, например, sizeof(char), а для определения размера конкретного значения оператор может использоваться без скобок. В программе использована функция puts(), которая записывает строку, адресуемую, например, параметром str, в стандартное выходное устройство – дисплей. Символ конца строки преобразуется в разделитель строк. Результат выполнения программы показан на рис. 6.1.
Задание 1 В строке str2[] уберите символ завершения строки. Проанализируйте и объясните результат. Вместо функций puts() примените функции printf(). Задайте явную инициализацию строк как одномерных массивов. Сравните результаты длины и размерности строк. При явной инициализации одномерных массивов в заданных строках не указывайте символы конца строки. Проанализируйте результат выполнения программы. Напишите программу циклического заполнения символьного массива, соответствующего фамилии пользователя (студента). Определите размерности типов: char, int, float, double, long int, long double, long long int. Выполните в цикле при использовании оператора типа переключатель.Пример 2. Напишите программу копирования одной заданной строки в другую. Для решения используем библиотечную функцию strcpy(), для которой подключим заголовок <string. h>. Программный код решения примера: #include <stdio. h> #include <conio. h> #include <string. h> int main (void) { char str1[] = ""; char str2[] = "qwerty"; printf("\n\t The length of the line \"%s\" is: %d\n", str1, strlen(str1)); strcpy(str1, str2); printf("\t After copying: "); puts(str1); printf("\n\t The length of the line \"%s\" is: %d\n", str1, strlen(str1)); printf("\n Press any key: "); _getch(); return 0; } Результат выполнения программы показан на рис. 6.2.
Задание 2 В программе для строк добавьте символ окончания строки. Проанализируйте результат выполнения программы. Скопированную строку выведите на экран дисплея с разрядкой (с пробелами) между символами. Произведите копирование одной строки в другую при их задании через клавиатуру. Введите с клавиатуры две строки, различающиеся одним символом. Напишите программу по определению индексов двух различных символов для введенных строк. Длину строк возьмите не менее 7 символов. Произведите копирование одной строки в другую без применения функции strcpy(). С клавиатуры введите свою фамилию (буквами латинского алфавита), выведите ее на консоль в прямом и обратном порядке, например, Ivanov, vonavI.Пример 3. Напишите программу преобразования десятичной системы счисления в двоичную. Исходное десятичное число считайте целым без знака. Программный код решения примера: #include <stdio. h> #include <conio. h> int main (void) { const char D[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}; int newNumber[80]; long int inputNumber; int i = 0, base = 2; printf("\n\t Enter a positive integer: "); scanf_s("%ld", &inputNumber); // Прямой процесс преобразования десятичного числа в двоичное do { newNumber[i] = inputNumber % base; ++i; inputNumber /= base; } while ( inputNumber!= 0 ); //Запись преобразованного числа в обратном порядке printf("\n\t Result after conversion: "); for (--i; i >= 0; --i) printf("%d", newNumber[i]); printf("\n\n Press any key: "); _getch(); return 0; } В программе использован квалификатор (спецификатор) типа const, который указывает компилятору, что символьный массив не может изменяться в программе. Результат выполнения программы показан на рис. 6.3.
Задание 3 Проверьте работу программы с помощью инженерного калькулятора (см. calc через Пуск–Выполнить). Подсчитайте число итераций цикла для преобразования числа 13 в двоичное число. Вместо цикла do–while примените цикл while. Напишите программу преобразования двоичного числа в десятичное число. Проверьте работу программы с помощью инженерного калькулятора. Проверьте программу для преобразования десятичных чисел в числа с основанием 3 и 8.Пример 4. С помощью функции strcat() присоедините одну строку к другой с пробелом и без. Пусть имеются строки str1 и str2. Функция strcat() присоединяет к строке str1 копию строки str2 и завершает строку str1 нулевым символом. Процесс присоединения называется конкатенацией. Программный код решения примера: #include <stdio. h> #include <conio. h> #include <string. h> #define N 79 int main (void) { char str1[N+1], str2[N+1]; printf("\n\t Print 1 string of characters: "); gets_s(str1, N); // для MS Visual Studio printf("\t Print 2 string of characters: "); gets_s(str2, N); // для MS Visual Studio strcat_s(str1, N, str2); // для MS Visual Studio printf("\n\t Result after concatenation: %s\n", str1); printf("\n Press any key: "); _getch(); return 0; } Возможный результат выполнения программы показан на рис. 6.4.
Задание 4 Дополните программу присоединением трех строк, например, имени, отчества и фамилии пользователя с учетом необходимых пробелов. Примените посимвольный ввод строк и произведите их конкатенацию с выводом на дисплей результата конкатенации. Произведите конкатенацию имени и фамилии пользователя с помощью операторов цикла, т. е. без помощи функции strcat(). Предусмотрите пробел между именем и фамилией. Присоедините к своему имени свою фамилию, записанную в обратном порядке.Пример 5. Для заданного двухмерного символьного массива сравните длину каждой строки друг с другом и вывести результат сравнения: 1-я строка меньше или больше 2-й строки, строки равны между собой. Для сравнения двух строк используем библиотечную функцию strcmp(), которая сравнивает в лексикографическом порядке две строки и возвращает целое значение, зависящее от результата сравнения [6.2]. Если первая строка меньше второй, то возвращается значение меньше нуля, если строки равны (по длине), то возвращается нуль, если первая строка больше второй, то возвращается число больше нуля. Программный код решения примера: #include <stdio. h> #include <conio. h> #include <string. h> // Для функции strcmp() #define n 2 #define m 80 int main (void) { int i, x; char str[n][m] = { "hello,", "Hello," }; printf("\n\t Array of strings:\n"); for (i = 0; i < n; ++i) printf("\t %d) %s\n\t (Length: %d)\n", i+1, str[i], strlen(str[i])); printf("\n\t Comparision of lines: "); for (i = 0; i < (n - 1); ++i) {x = strcmp(str[i], str[i+1]); printf("\n\t Return the function of \"strcmp()\": %d", x); if (x == 0) printf("\n Line %d is equal to %d? nd line.", i+1, i+2); else if (x > 0) printf("\n Line %d is not equal to %d? nd line.", i+1, i+2); else if (x < 0) printf("\n Line %d is not equal to %d-nd line.", i+1, i+2); } printf("\n\n Press any key: "); _getch(); return 0; } Результат выполнения программы показан на рис. 6.5.
Задание 5 Произведите последовательное изменение букв во второй строке: каждую строчную букву замените прописной, записывая при этом оставшиеся буквы строчными. Проанализируйте результат выполнения программы. Протестируйте программу при изменении длины одной из строк, т. е. когда в одной строке не писать запятую. Внесите изменения в программу для совпадения двух строк. В символьный массив добавьте еще одну строку. Произведите сравнение между собой всех трех строк массива. Напишите программу по выводу на консоль информации о человеке с помощью пароля, который вводится с клавиатуры. Информация должна содержать имя, фамилию, год, месяц и число рождения, место рождения. В качестве пароля возьмите свою фамилию с учетом регистра (т. е. прописные или строчные буквы).Пример 6. Напишите программу создания символьного трехмерного массива, когда, например, на одной странице имеются три строки и на второй странице имеются также три строки. Предусмотрите изменение регистра в выводе на экран строк, когда строчные буквы становятся прописными и наоборот. Для перевода регистра используем библиотечные функции tolower() и toupper() с подключением библиотеки <ctype. h>. Программный код решения примера: #include <stdio. h> #include <conio. h> #include <string. h> #include <ctype. h> #define n 3 #define m 2 #define N 79 int main (void) { int i, k; // Определение и инициализация символьного массива char str[n][m][N] = { {"department: fet", "Specialization: acouY"}, {"Course: 1st", "SURNAME: ivAnov"}, {"Group: 141", "nAmE: Peter"} }; // Построчный вывод 1-й страницы printf("\n\t 1?st page:\n"); for (i = 0; i < n; ++i){ printf("\t"); printf("%s\n", str[i]); } // Посимвольный вывод 2-й страницы printf("\n\t 2?nd page:\n"); for (i = 0; i < n; ++i) { printf("\t"); for (k = 0; k < N; ++k) printf("%c", toupper(str[i][m-1][k])); // или tolower() } printf("\n\n Press any key: "); _getch(); return 0; } В программе используется трехмерный массив размера 3 Результат выполнения программы показан на рис. 6.6.
Задание 6 Примените функцию tolower() для перевода в строчные буквы. Для построчного вывода 1-й страницы используйте две размерности заданного массива. В программу добавьте нумерацию строк, например, 1), 2), 3). Предусмотрите вывод всей первой страницы, а для второй страницы – только 1-ю и 3-ю строки. Напишите программу с обработкой трехмерного символьного массива размера 2Пример 7. В символьной строке находятся слова и два числа, разделенные пробелами или запятыми. Выделите из строки слова и числа, разместите их в отдельных массивах. При этом считанные числа и слова разместите в дополнительных символьных массивах. Выведите значения сформированных символьных массивов. Произведите преобразование строковых (символьных) чисел к числам с плавающей точкой (тип double). Если после десятичной точки находится нуль (или нули), то число определите как целое, в противном случае – как число с плавающей точкой, т. е. типа double. Для решения примера используем библиотечные функции isalpfa() для определения буквы во входном потоке (с подключением библиотеки ctype. h), isspace() – для определения пробелов во входном потоке (с подключением библиотеки ctype. h), isdigit() – для определения цифры во входном потоке (с подключением библиотеки ctype. h), atof() – для преобразования строки чисел в число с плавающей точкой (с подключением библиотеки stdlib. h), atoi() – для преобразования строки чисел в целое число (с подключением библиотеки stdlib. h), modf() – для выделения целой и дробной части числа (с подключением библиотеки math. h), strlen() – для определения длины строки (с подключением библиотеки string. h). Программный код решения примера: #include <stdio. h> #include <conio. h> #include <string. h> #include <stdlib. h> #include <ctype. h> #include <math. h> #define N 79 int main (void) { double r = 0.0, b3, b4; int i, j, k, m, n, A[N], n2, n3, n4; char str[N], str2[N], str3[N], str4[N]; // Множественная инициализация i = j = k = m = 0; // Обнуление символьных массивов for (n2 = 0; n2 < N; ++n2) str2[n2] = 0; for (n3 = 0; n3 < N; ++n3) str3[n3] = 0; for (n4 = 0; n4 < N; ++n4) str4[n4] = 0; printf("\n\t Print line with 2-nd numbers:\n\t "); gets_s(str, sizeof(str)/sizeof(str[0])); printf("\n\t The line is:\n"); printf("\t %s\n", str); while (str[i] != '\0') { if ( isalpha(str[i]) || isspace(str[i]) ) str2[j++] = str[i]; else if (isdigit(str[i]) || str[i] == '.') A[k++] = i; // массив индексов цифр потока else str2[j++] = str[i]; ++i; } printf("\n\t A string of words and symbols:\n\t"); puts(str2); if (k > 0) { n = 0; for (i = 0; i < k; ++i){ if (A[i + 1] - A[i] == 1 ) str3[n++] = str[A[i]]; else if (A[i+1] - A[i] > 1) {m = A[i+1]; str3[n++] = str[A[i]]; break;} } } printf("\n"); if (n > 0) { r = modf(atof(str3), &b3); if (!r) // если не нуль printf("\t The number is an integer: %d", atoi(str3)); else printf("\t The number is real (double): %lf\n", atof(str3)); } if (m > 0 ) { j = 0; for (i = m; i <= A[k - 1]; ++i) str4[j++] = str[i]; r = modf(atof(str4), &b4); if (!r) // если не нуль printf("\t The number is an integer: %d", atoi(str4)); else printf("\t The number is real (double): %lf\n", atof(str4)); } printf("\n\n Press any key: "); _getch(); return 0; } В программе функция modf() возвращает величину дробной части числа (переменная r) и целой части (от переменных b3, b4 взяты их адреса, т. е. &b3, &b4). Анализ массива с индексами цифр исходного символьного массива дает возможность выделить индексацию двух чисел входного потока. Функция gets_s() автоматически добавляет символ конца строки '\0', поэтому при объявлении размерности символьного массива следует предусмотреть одно место для '\0'. Для переносимости программ размерность массива в функции gets_s() определена с помощью функции sizeof(). Возможный результат выполнения программы показан на рис. 6.7.
Задание 7 Условие оператора while сделайте короче. Ввести свою фамилию (буквами латинского алфавита), год рождения (числом), месяц рождения (словом) и приблизительный свой вес с точностью до долей килограмма. Результат выполнения программы занесите в отчет лабораторной работы. Вместо переменных и массива типа double примените тип float. Проверьте работоспособность программы. Вместо оператора break примените оператор безусловного перехода goto. Проверьте работоспособность программы. Напишите программу при вводе строки с несколькими словами и одним числом (с десятичной точкой или без нее). Напишите программу при вводе строки с несколькими словами и тремя числами (с десятичной точкой или без нее).Пример 8. Напишите программу, в которой генерируется строка символов заданного размера (более трех) и для которой определяется подстрока из трех символов, вводимой пользователем. В случае, когда подстрока не обнаружена, предусмотреть генерирование случайной строки поиска 1000 раз, и программа должна искать подстроку до первого совпадения. Программный код решения примера: #include <stdio. h> #include <conio. h> #include <string. h> #include <stdlib. h> #include <time. h> int main (void) { int i, j, k, n, N = 1000, numA, numZ, in, ch_box[3]; char str[16], str2[4]; srand((unsigned)time(NULL)); numA = (int)'a'; // числовой код латинской буквы а numZ = (int)'z'; // числовой код латинской буквы z printf("\n Enter a string of 3 letters: "); in = scanf_s("%s", str2, sizeof(str2)); if ( in == 0 ) { printf("\n Error input. Print any key: "); _getch(); exit(1); } printf("\n\t substring is \"%s\"\n", str2); for (n = 0; n < N; n++) { for (i = 0; i < 15; i++) str[i] = numA + rand() % (numZ - numA) + 1; str[i] = '\0'; k = 0; for (i = 0; i < 13; i++) if (str2[0] == str[i] && str2[1] == str[i+1] && \ str2[2] == str[i+2]) { ch_box[0] = i; ch_box[1] = i+1; ch_box[2] = i+2; k++; break; } if (k > 0) break; } if (k == 0) printf("\n\t \"%s\" not found", str2); else printf("\n Substring \"%s\" found at positions %d, %d, %d", \ str2, ch_box[0]+1, ch_box[1]+1, ch_box[2]+1); puts("\n"); for (k = 0; k < 15; k++) printf(" %3d)", k+1); puts(""); for (j = 0; j < 15; j++) printf(" %3c ", str[j]); printf("\n\n... Press any key: "); _getch(); return 0; } букв. Интервал чисел определяется между кодом буквы 'a' и кодом буквы 'z'. Возможный положительный результат выполнения программы показан на рис. 6.8.
Задание 8 Условие нахождения подстроки в строке выполните с помощью оператора goto. В случае положительного поиска подстроки в строке, определите количество итераций цикла с предельным значением 1000. Предусмотрите, чтобы вводимые буквы могли быть и нижнего и верхнего регистров. В программе сделайте ввод с клавиатуры количество циклов формирования строки, в которой следует найти заданную подстроку. Сделайте поиск подстроки в строке, которую сформировали случайно один раз. Если подстроки не будет найдено, предусмотрите ввод количество попыток поиска для генерируемых строк (в 15 символов).Контрольные вопросыКакой размер одномерного символьного массива должен быть объявлен для записи в него строки из 5 символов? Как организуются многомерные символьные массивы в языке С? Как организуется индексирование символьных массивов в языке С? На кого или на что возлагается контроль границ символьных массивов в языке С? Как следует объявить символьный массив для записи в него трех строк различной длины? Как следует организовать посимвольное заполнение одномерного массива? Сколько потребуется операторов цикла для вывода на консоль двухмерного символьного массива (строк различной длины)? Какие функции используются для считывания с консоли символьных массивов? Для каких функций включаются в программу заголовочные файлы ctype. h и string. h? Что такое лексиграфическое упорядочивание строк? Чем отличается сравнение строк при помощи оператора "==" (равно) и функции strcmp()? |
|
Программирование на языке C в Microsoft Visual Studio 2010 |
|
|
|
7. Лекция: Указатели в языке программирования С: |
|
|
|
Теоретическая частьПо краткому определению, указатель – это переменная, содержащая адрес другой переменной [7.1]. Так как указатель содержит адрес переменной (объекта), это дает возможность "косвенного" доступа к этой переменной (объекту) через указатель. Для понимания работы и назначения указателей рассмотрим упрощенную схему организации памяти компьютера. Память представляет собой массив последовательно пронумерованных или адресованных ячеек, с которыми можно работать по отдельности или связанными кусками. Известно, что различным типам данных отводится определенное количество байтов памяти. Поэтому указатель – это группа ячеек, в которых может храниться адрес. Например, если переменная ch имеет тип char, а ptr (от латинского pointer – указатель) есть указатель на переменную ch, то взятие адреса переменной ch осуществляется с помощью унарного (одноместного) оператора &, т. е. ptr = &ch; Приведенная инструкция означает, что переменной ptr присваивается адрес ячейки ch. Принято считать, что ptr указывает на ch. Оператор & применяется только к объектам, расположенным в памяти: к переменным, элементам массива. Операндом оператора & не может быть ни выражение, ни константа, ни регистровая переменная [7.1]. Унарный оператор & называется еще оператором адресации [7.2]. Имена указателям даются в соответствии с правилами, приятыми в языке программирования С для обычных переменных. Другая унарная операция * называется операцией ссылки по указателю (indirection), или разыменования (dereferencing). Если применить ее к указателю, то получим объект, на который он указывает. Рассмотрим пример. Пусть х и у – целые переменные, а *ptr – указатель на целую переменную. Поставим задачу присвоения переменной у значения переменной х с помощью указателя. Фрагмент С-кода будет следующий: int x = 1, y = 2; int *ptr; // объявили указатель на целую переменную ptr = &x; // взяли адрес переменной х = 2 y = *ptr; // переменная у стала равной 1 *ptr = 0; // переменная х стала равной 0 В приведенных объявлениях новым является объявление указателя: int *ptr; Следует помнить, что любой указатель может указывать только объекты одного конкретного типа данных, заданного при объявлении [7.1]. Унарный оператор * есть оператор косвенного доступа. Примененный к указателю он выдает объект, на который данный указатель указывает. Одноместные (унарные) операции * и & имеют более высокий приоритет для своих операндов, чем арифметические операции. Для указателей одного типа можно, например, выполнять присваивание без разыменования. Это вытекает из того, что указатели сами по себе являются переменными. Пусть определен еще один указатель типа int, например, ptr2. Тогда возможно произвести присвоение: ptr2 = ptr; После присвоения указатель ptr2 будет указывать на ту же переменную, что и указатель ptr. В языке С допустимы следующие (основные) операции над указателями: присваивание; получение значения того объекта, на который ссылается указатель (синонимы: косвенная адресация, разыменование, раскрытие ссылки); получение адреса самого указателя; унарные операции изменения значения указателя; аддитивные операции и операции сравнений (отношений) [7.3]. С помощью унарных операций "++" и "––" числовые (арифметические) значения переменных типа указатель меняются по-разному в зависимости от типа данных, с которыми связаны эти переменные [7.3]. Если указатель связан с типом char, то при выполнении операций "++" и "––" его числовое значение изменяется на 1 (единицу). Если указатель связан с типом int, то операции "++" и "––" изменяют числовые значения указателей на 2. Указатель, связанный с типами float или long, унарными операциями "++" и "––" изменяется на 4. Таким образом, при изменении указателя на единицу указатель "переходит к началу" следующего (или пр едыдущего) поля той длины, которая определяется типом. Следует особо остановиться на указателях и квалификаторе (модификаторе) const. Как известно, квалификатор const превращает переменную в константу, значение которой не должно меняться. Например, нет смысла изменять число long value = 9999L; const long *pvalue = &value; Последняя строчка приведенного кода определяет собой указатель на константу. Попытка указателю pvalue присвоить иное числовое значение будет восприниматься компилятором как ошибка [7.6]. Но само значение переменной value изменять допустимо. При этом указатель держит адрес переменной, значение которой изменилось. В тоже время саму переменную также можно объявить с помощью квалификатора const. В этом случае нельзя изменять ни переменную, ни значение указателя (т. е. присвоить иное числовое значение указателю). Указатели на константы часто используются как формальные параметры функций (о функциях будет сказано позднее). Константный указатель может адресовать как константу, так и переменную. В случае, когда определен константный указатель, то через него нельзя уже брать адрес другой переменной. Приведем следующий пример определения константного указателя: int count = 43; int *const pcount = &count; Вторая строчка приведенного кода определяет и инициализирует константный указатель pcount, который "привязан" к адресу переменной count. Если определить новую переменную того же типа, то взять адрес новой переменной с помощью константного указателя pcount будет нельзя, компилятор сделает сообщение об ошибке и работа программы будет невозможной. В тоже время возможно изменить значение константного указателя через другое числовое значение. Но это повлечет за собой изменение переменной, на которую указатель ссылается. Например, int count = 43; int *const pcount = &count; pcount = 345; В соответствии с приведенным кодом переменная count будет иметь значение 345 [7.6]. Соответственно, если константный указатель ссылается на константный объект (например, на константную переменную), то в этом случае ни значение объекта, на который ссылается такой указатель, ни значение самого указателя (когда будет сделана попытка присвоить иное числовое значение указателю) не может быть изменено в программе. Например, const int card = 21; const int *const pcard = &card Указанные особенности для указателей с квалификатором const присущи и для переменных (объектов) других типов. Указатели, значения которых изменять нельзя используются, например, при заполнении константных таблиц. Практическая частьПример 1. Напишите программу определения адресов целых чисел от 0 до 9 и строчных букв латинского алфавита. Программный код решения примера: #include &stdio. h> #include &conio. h> int main (void) { int i, j = 0; char c = 'a', *ptr2; ptr2 = &c; printf("\n\t Figures, symbols and their addresses:\n"); for (i = 0; i < 10; ++i) printf("\n\t %3d) %2d --> %5p", i + 1, i, &i); printf("\n"); for ( ; *ptr2 <= 'z'; (*ptr2)++) printf("\n\t %3d) %2c --> %5p", ++j, *ptr2, ptr2); printf("\n\n Press any key: "); _getch(); return 0; } Результат выполнения программы показан на рис. 7.1.
В программе использован спецификатор формата %5p для определения адреса переменных. Число 5 определяет отступ от левого края на пять позиций. Задание 1 Добавьте вывод кодов цифр и букв, для которых определены адреса в памяти компьютера. В программе вместо операторов цикла for примените операторы while. В программу введите указатель на тип int и применить этот указатель по аналогии с указателем *ptr2. Добавьте определение адресов прописных букв латинского алфавита и вывести их дополнительным столбцом к адресам строчных букв. Выведите в столбец свою фамилию (буквами латинского алфавита), имя и адреса соответствующих букв фамилии и имени.Пример 2. Напишите программу однозначного задания типа разностей указателей, и определения адресов заданных указателей. Для решения данного примера подключим заголовок stddef. h для определения типа разности указателей с помощью зарезервированного имени типа ptrdiff_t. Программный код решения примера: #include <stdio. h> #include <conio. h> #include <stddef. h> int main (void) { int x, y; int *px, *py; ptrdiff_t z; // инициализация указателей px = &x; py = &y; // разница двух указателей z = px - py; printf("\n The difference of two pointers to %p and %p is: %d", px, py, (int) z); printf("\n\n The addresses are: px = %p, py = %p\n", &px, &py); printf("\n Press any key: "); _getch(); return 0; } Результат выполнения программы показан на рис. 7.2.
Задание 2 Поменяйте местами переменные х и у. Проанализируйте результат выполнения программы. Для переменных произведите инициализацию в соответствии с номером компьютера, за которым выполняется лабораторная работа, и своего дня рождения. Рассмотрите решение примера для следующих типов: char, long int, long long int, unsigned int, float, double, long double. Вывод результатов осуществите с помощью одной функции printf().Пример 3. Напишите программу арифметических операций с указателями. При выполнении примера следует иметь в виду, что операции "&" и "*" имеют более высокий приоритет, чем обычные арифметические операции. Программный код решения примера: #include <stdio. h> #include <conio. h> int main (void) { int x = 2, y = 7, a, b, *ptr, *ptr2; ptr = &a; ptr2 = &b; *ptr = x - y; *ptr2 = y - x - *ptr + 100; printf("\n\t Arithmetic operations with pointers:\n"); printf("\t a = %d, b = %d\n", a, b); printf("\n Press any key: "); _getch(); return 0; } Результат выполнения программы показан на рис. 7.3.
Следует обратить внимание на то, что переменные a и b сначала не были определены, а в результате стали иметь некоторые значения. Задание 3 В программе примените типы double и float. После взятия адресов от переменных a и b, измените значения указателей на значения Х и 10*Х, где Х – номер компьютера, за которым выполняется лабораторная работа. Выполните указанные арифметические действия и выведите значения переменных a и b. Напишите программу для выполнения операций вычитания, умножения и деления с участием указателей. Для вывода результатов примените только одну функцию printf().Пример 4. Напишите программу двухуровневой адресации для объектов целого типа. Случай, когда указатель ссылается на указатель, который ссылается на число, называется многоуровневой адресацией [7.4]. В случае двухуровневой адресации первый указатель содержит адрес второго указателя, который содержит адрес объекта с нужным значением. Объявление указателя на указатель делается с помощью двух звездочек перед именем переменной. Программный код решения примера: #include <stdio. h> #include <conio. h> int main (void) { int x, y = 8; int *ptr, **ptr2; x = 7; ptr = &x; ptr2 = &ptr; **ptr2 = *ptr + 10; printf("\n\t The value of x = %d. 1 st pointer is: %d. 2?nd pointer is: %d\n", x, *ptr, **ptr2); ptr = &y; ptr2 = &ptr; **ptr2 = 88; printf("\n\t The value of y = %d\n", y); printf("\n Press any key: "); _getch(); return 0; } Результат выполнения программы показан на рис. 7.4.
Задание 4 Выведите на экран пользователя адреса указателей. Организуйте цикл инкрементирования первого указателя начиная с Х до 10*Х, где Х – номер компьютера, за которым выполняется лабораторная работа. Сделайте вывод значений переменной, на которую делает ссылку первый указатель, и значений второго указателя. Напишите программу трехуровневой адресации при задании целых чисел, равных Х и 10*Х, где Х – номер компьютера, за которым выполняется лабораторная работа.Пример 5. Напишите программу по определению и инициализации переменных разных типов и одного указателя типа void *. Последовательно присваивая указателю адреса переменных, выведите значения переменных с помощью разыменования указателя [7.5]. Программный код решения примера: #include <stdio. h> #include <conio. h> int main (void){ int x = 99; double y = 6.78; char symbol = '#'; void *ptr; ptr = &x; printf("\n\t The value of variable through a pointer: %d\n", *(int *) ptr); ptr = &y; printf("\n\t The value of variable through a pointer: %lf\n", *(double *) ptr); ptr = &symbol; printf("\n\t The value of variable through a pointer: %c\n", *(char *) ptr); printf("\n Press any key: "); _getch(); return 0;} Результат выполнения программы показан на рис. 7.5.
Особенностью использования указателя типа void является то, что при разыменовании указателя необходимо производить преобразования типов. Прежде чем произвести разыменование указателя, его приводят к указателю соответствующего типа. Задание 5 В программу добавьте переменные типа float, unsigned, long и обеспечьте ввод их значений с клавиатуры. Выведите адреса и значения переменных с помощью разыменования указателя. Задайте порядок (нумерованную последовательность) инициализации переменных и создайте вывод значений указателя на основе переключателя switch. Номер инициализируемой переменной задайте с клавиатуры. В программу введите операцию двухуровневой адресации с применением указателя типа void. Выведите значения двух указателей с помощью их разыменования.Пример 6. Напишите программу по реализации условия: определить и инициализировать переменную типа double. Определите указатели типа char *,int *,double *,void *, инициализируйте их адресом переменной. Выведите на экран пользователя значения указателей, их размеры и длины участков памяти, которые связаны с выражениями, разыменовывающими указатели [7.5]. Программный код решения примера: #include <stdio. h> #include <conio. h> int main (void) { double d = 6.78; char *cp; int *ip; double *dp; void *vp; // Адресация с приведением типов cp = (char *)&d; ip = (int *)&d; dp = (double *)&d; vp = &d; printf("\n\t Address:\n\t char = %p\n\t int = %p\n\t double = %p\n\t void = %p\n", cp, ip, dp, vp); // Размеры указателей и памяти разыменованных указателей: printf("\n\t The dimension of the object type \"pointer\":\n\t char = %d\n\t int = %d\n\t double = %d\n\t void = %d\n", sizeof(cp), sizeof(ip), sizeof(dp), sizeof(vp)); printf("\n\t The size of the memory pointer:\n\t char = %d\n\t int = %d\n\t double = %d\n", sizeof(*cp), sizeof(*ip), sizeof(*dp)); printf("\n Press any key: "); _getch(); return 0; } Результат выполнения программы показан на рис. 7.6.
Как видно из полученного результата, размеры участков памяти, выделенных указателям разных типов, одинаковы. В программу добавьте вывод размера памяти для разыменованного указателя типа void. Выведите значения указателей заданных типов. Определите указатель с правильным доступом к значению переменной d = 6.78. Объявление указателей и взятие адреса сделайте в одной строчке для соответствующего типа. В программу добавьте строки по вводу целого, вещественного типов данных, а также одиночного символа. Затем с помощью указателей выведите на консоль значения введенных данных.Пример 7. Напишите программу, в которой с помощью указателя и функции scanf_s() читаются данные с клавиатуры, а также определяются и инициализируются указатели на константы и константные указатели. Программный код решения примера: #include <stdio. h> #include <conio. h> #include <math. h> #include <stdlib. h> #include <time. h> int main (void) { double x, *px = &x, e = exp(1); const double pi = acos(0.0); const double *pexp = &e; const int base = 10; const int *const pbase = &base; const double *ptr_pi = π int i; time_t t; printf("\n Enter a real number: "); scanf_s("%lf", px); printf("\n The value of the entered number is \"%g\"\n", x); printf("\n The base of natural logarithms \ is \"%0.14f\"\n", *pexp); printf("\n The base of the decimal logarithm is \"%d\"\n",\ *pbase); srand((unsigned) time(&t)); // рандомизация for (i = 0; i < rand(); i++) { rand(); } // Случайное вещественное число из интервала [-100.0; 100.0] x = -100.0 + (10* (double)rand() / RAND_MAX; printf("\n The modified value of x: %g\n \ Pointer to the variable x: %g\n", x, *px); printf("\n The value of pi through the pointer \ and the function acos(0): %0.14f\n", *ptr_pi * 2); printf("\n\n... Press any key: "); _getch(); return 0; } В программе для получения числа пи ( Возможный результат выполнения программы показан на рис. 7.7.
Задание 7 Выполните инкрементирование указателей на константы и константных указателей. Объясните полученный результат. Введите в программу двойной ввод с клавиатуры вещественного числа. Первый раз с помощью указателя, а второй раз с помощью переменной. В обоих случаях вывести на печать значения указателя. В качестве константы примите некоторое шестнадцатеричное число (с буквами). Определите указатель на константу и в цикле от 1 до 16 измените значение указателя с последующим выводом результатов на консоль. Выполните возможные арифметические операции с константными указателями и с указателями на константы. В программу введите строковую переменную, определенную через фамилию (буквами латинского алфавита) автора закона всемирного тяготения. Определите указатель на константу и выведите фамилию на консоль через указатель. Затем в цикле введите известные вам фамилии трех лауреатов нобелевской премии по литературе. Вывод результатов на консоль выполните с помощью указателя.Контрольные вопросыКакое общее назначение указателей в языке С? Какие арифметические операции допускаются для указателей? Какие унарные операторы используются с указателями? Как они называются? Для каких типов данных может быть использован указатель? Как числовые значения указателей изменяются при их инкрементировании в зависимости от типов данных. С помощью какого формата осуществляется вывод на консоль адресов переменных заданного типа? Что такое многоуровневая адресация? Как она организуется в языке С? Как осуществляется инициализация указателей на вещественные типы данных? Как осуществляется инициализация указателей на символьный тип данных? Какой смысл имеет значение указателя NULL? |
|
Программирование на языке C в Microsoft Visual Studio 2010 |
|
|
|
8. Лекция: Указатели и массивы в языке С: |
|
|
|
Теоретическая часть Одной из наиболее распространенных конструкций с использованием указателей являются массивы [8.1]. Результатом использования указателей для массивов является меньшее количество используемой памяти и высокая производительность [8.1]. По краткому определению, указатель – это переменная, содержащая адрес другой переменной [8.2]. Так как указатель содержит адрес переменной (объекта), то это дает возможность "косвенного" доступа к этой переменной (объекту) через указатель. В качестве объектов в данной лабораторной работе будут рассмотрены числовые и символьные массивы. В языке С массивы – это упорядоченные данные (элементы) одного типа. Компилятор языка С рассматривает имя массива как адрес его первого элемента (в языке С нумерация элементов массива начинается с нуля). Например, если имя массива Arr с десятью элементами, то i-й элемент (0 Имя массива без индекса образует указатель на начало этого массива. Следует помнить следующее: отличие имени массива от указателя состоит в том, что имя массива не может быть изменено. Имя массива всегда указывает на одно и то же место в памяти – на нулевой элемент. Пусть, например, массив Arr содержит 10 целочисленных переменных: int Arr[10]; Тогда можно объявить указатель ptr, который будет указывать на элементы массива Arr: int *ptr; Тип указателя (в примере это int) должен соответствовать типу объявленного массива. Для того, чтобы указатель ptr ссылался на первый элемент (с нулевым индексом) массива Arr, можно использовать утверждение: ptr = Arr; В то же время можно использовать прямую адресацию: ptr = &Arr[0]; Обе формы записи эквивалентны. Аналогичные утверждения будут справедливы для других типов массивов: char, float, double и пр. Если указатель ptr указывал на первый элемент (с нулевым индексом) массива Arr, то для обращения к следующему элементу массива допустимы следующие формы утверждений: ptr = &Arr[1]; ptr += 1; Соответственно, выражение *(ptr+1) будет ссылаться на значение, содержащееся в элементе Arr[1]. Утверждение ptr += n; заставит указатель *ptr ссылаться на элемент массива, находящийся на расстоянии n от того, на который ранее ссылался указатель, независимо от типа элементов, содержащихся в массиве [8.1]. Разумеется, значение n должно быть в допустимых пределах для данного объема массива. При работе с указателями и массивами особенно удобны операторы инкремента "++" и декремента "––" [8.1]. Использование оператора инкремента с указателем аналогично операции суммирования с единицей, а операция декремента имеет тот же эффект, что и вычитание единицы из указателя. В языке программирования С вполне корректной операцией является сравнение указателей. К указателям применяются операции сравнения ">", ">=", "!=", "==", "<=", "<" [3]. Сравнивать указатели допустимо только с другими указателями того же типа или с константой NULL, обозначающей значение условного нулевого адреса. Константа NULL – это особое значение переменной-указателя, присваиваемое ей в том случае, когда она не должна иметь никакого значения. Его можно присвоить переменной-указателю любого типа. Оно представляет собой целое число нуль. Особое значение имеет сравнение двух указателей, которые связаны с одним и тем же массивом данных. Рассмотрим инициализацию указателей типа char: char *ptr = "hello, world"; Переменная *ptr является указателем, а не массивом. Поэтому строковая константа "hello, world" не может храниться в указателе *ptr. Тогда возникает вопрос, где хранится строковая константа. Для этого следует знать, что происходит, когда компилятор встречает строковую константу. Компилятор создает так называемую таблицу строк. В ней он сохраняет строковые константы, которые встречаются ему по ходу чтения текста программы [8.4]. Следовательно, когда встречается объявление с инициализацией, то компилятор сохраняет "hello, world" в таблице строк, а указатель *ptr записывает ее адрес. Поскольку указатели сами по себе являются переменными, их можно хранить в массивах, как и переменные других типов [8.2]. Получается массив указателей. Массив указателей фиксированных размеров вводится одним из следующих определений [8.4]: тип *имя_массива [размер]; тип *имя_массива [ ] = инициализатор; тип *имя_массива [размер] = инициализатор; В данной инструкции тип может быть как одним из базовых типов, так и производным типом; имя_массива – идентификатор, определяемый пользователем по правилам языка С; размер – константное выражение, вычисляемое в процессе трансляции программы; инициализатор – список в фигурных скобках значений элементов заданного типа (т. е. тип). Рассмотрим примеры [8.4]: int data[7]; // обычный массив int *pd[7]; // массив указателей int *pi[ ] = { &data[0], &data[4], &data[2] }; В приведенных примерах каждый элемент массивов pd и pi является указателем на объекты типа int. Значением каждого элемента pd[j] и pi[k] может быть адрес объекта типа int. Все 6 элементов массива pd указателей не инициализированы. В массиве pi три элемента, и они инициализированы адресами конкретных элементов массива data. В случае обработки строк текста они, как правило, имеют различную длину, и их нельзя сравнить или переместить одной элементарной операцией в отличие от целых чисел. В этом случае эффективным средством является массив указателей. Например, если сортируемые строки располагаются в одном длинном символьном массиве вплотную — начало одной к концу другой, то к каждой строке можно обращаться по указателю на ее первый символ [8.2]. Сами же указатели можно поместить в массив, т. е. создать массив указателей. Две строки можно сравнить, рассмотрев указатели на них. Массивы указателей часто используются при работе со строками. Можно привести пример массива строк о студенте, задаваемый с помощью массива указателей. char *ptr[ ] = { "Surname", //Фамилия "Name", // Имя "group", // группа "ACOUY" // специальность }; С помощью массива указателей можно инициализировать строки различной длины. Каждый из указателей массива указателей указывает на одномерный массив символов (строку) независимо от других указателей. В языке программирования С предусматриваются ситуации, когда указатели указывают на указатели. Такие ситуации называются многоуровневой адресацией. Пример объявления указателя на указатель: int **ptr2; В приведенном объявлении **ptr2 – это указатель на указатель на число типа int. При этом наличие двух звездочек свидетельствует о том, что имеется двухуровневая адресация. Для получения значения конкретного числа следует выполнить следующие действия: int x = 88, *ptr, **ptr2; ptr = &x; ptr2 = &ptr; printf("%d", **ptr2); В результате в выходной поток (на дисплей пользователя) будет выведено число 88. В приведенном фрагменте переменная *ptr объявлена как указатель на целое число, а **ptr2 – как указатель на указатель на целое. Значение, выводимое в выходной поток (число 88), осуществляется операцией разыменования указателя **ptr2. Для многомерных массивов указатели указывают на адреса элементов массива построчно. Рассмотрим пример двухмерного целочисленного массива М размера 3 int M[3][5]= {{1,2,3,4,5},{–6,–7,–8,–9,–10},{11,12,13,14,15}}; int *ptr; Элементы массива (по индексам) располагаются в ячейках памяти по строкам в следующем порядке: M[0][0], M[0][1], M[0][2], M[0][3], M[0][4], M[1][0], M[1][1], M[1][2], M[1][3], M[1][4], M[2][0], M[2][1], M[2][2], M[2][3], M[2][4]. Сначала запоминается первая строка, затем вторая, затем третья. В данном случае двухмерный массив – это массив трех одномерных массивов, состоящих из 5 элементов. Указатель указывает на адреса элементов в порядке расположения их в памяти. Поэтому тождественны равенства: ptr == &M[0][0]; //?1-я строка, 1-й столбец ptr + 1 == &M[0][1]; // 1-я строка, 2-й столбец ptr + 2 == &M[0][2]; // 1-я строка, 3-й столбец ptr + 3 == &M[0][3]; // 1-я строка, 4-й столбец ptr + 4 == &M[0][4]; // 1-я строка, 5-й столбец ptr + 5 == &M[1][0]; // 2-я строка, 1-й столбец ptr + 6 == &M[1][1]; // 2-я строка, 2-й столбец ptr + 7 == &M[1][2]; // 2-я строка, 3-й столбец ptr + 8 == &M[1][3]; // 2-я строка, 4-й столбец ptr + 9 == &M[1][4]; // 2-я строка, 5-й столбец ptr + 10 == &M[2][0]; // 3-я строка, 1-й столбец ptr + 11 == &M[2][1]; // 3-я строка, 2-й столбец ptr + 12 == &M[2][2]; // 3-я строка, 3-й столбец ptr + 13 == &M[2][3]; // 3-я строка, 4-й столбец ptr + 14 == &M[2][4]; // 3-я строка, 5-й столбец Практически следует произвести инициализацию указателя, например, взяв адрес первого элемента матрицы, а затем – обращение к элементам матрицы, можно производить через указатель: ptr = &M[0][0]; *(ptr + i*n + j); где i – номер строки заданной матрицы, j – номер столбца, n – число столбцов в матрице. Практическая часть Пример 1. Напишите программу считывания строк разной длины с использованием арифметики указателей. Программный код решения примера: #include <stdio. h> #include <conio. h> int main (void) { int i, n; char *ptr[ ] = {"one", "two", "three", "four", "five",\ "six", "seven", "eight", "nine", "ten"}; n = sizeof(ptr)/sizeof(ptr[0]); printf("\n\t Strings of various length:\n"); for (i = 0; i < n; ++i) printf("\n%12d) %s", i+1, ptr[i]); printf("\n\n Press any key: "); _getch(); return 0; } В программе использован одномерный массив указателей. Функция printf() и спецификатор преобразования %s допускают использование в качестве параметра указатель на строку. При этом на дисплей выводится не значение указателя, а содержимое адресуемой им строки. Обратный слэш "\" служит для переноса содержимого операторной строки на новую строку (для удобства восприятия). Оператор sizeof() вычисляется во время компиляции программы. Во время компиляции он обычно превращается в целую константу, значение которой равно размеру типа или объекта [8.4], в данном случае соответствует размеру массива указателей. Следует обратить внимание на инициализацию массива указателей. Содержимое, заключенное в фигурные скобки, представляют собой строки, для каждой из которой служит указатель, входящий в массив указателей. Результат выполнения программы показан на рис. 8.1.
Задание 1 Вывод заданных строк осуществите на экран дисплея в одной строке. Вывод результата выполните на основе разыменования массива указателей. Инициализируйте массив указателей своей фамилией, именем, номером группы, специальности, факультета и номером компьютера, за которым выполняется лабораторная работа. В программу введите дополнительный массив указателей, с помощью которого выполните вывод заданных строк. В программе вместо оператора цикла for применить оператор while.Пример 2. Напишите программу сортировки одномерного массива, состоящего из 10 равномерно распределенных случайных чисел из интервала [–8; 8], с помощью указателей. Программный код решения примера: #include <stdio. h> #include <conio. h> #include <stdlib. h> #include <time. h> #define N 10 int main (void) { double a = -8.0, b = 8.0; double arr[N], *pmin[N], *temp; int i, j; long int T; T = (long)time(NULL); // использование системного времени srand((unsigned int) T); // Заполнение массива случайными числами for(i = 0; i < N; ++i) arr[i] = a + (b - a)*(double)rand()/RAND_MAX; printf("\n\t The initial array of [%1.4f, %1.4f]:\n", a, b); for (i = 0; i < N; ++i) printf("\n\t%2d) %8.4f", i+1, arr[i]); // Взятие адресов элементов исходного массива //в предположении, что они образуют отсортированный массив for (i = 0; i < N; ++i) pmin[i] = &arr[i]; //Сортировка массива по убыванию for (i = 0; i < N-1; ++i) for (j = i+1; j < N; ++j) { if (*pmin[i] < *pmin[j]) { temp = pmin[i]; pmin[i] = pmin[j]; pmin[j] = temp; } } //Вывод отсортированного массива по убыванию printf("\n\n\t Assorted array of descending:\n"); for (i = 0; i < N; ++i) printf("\n\t%2d) %8.4f", i+1, *pmin[i]); printf("\n\n Press any key: "); _getch(); return 0; } В программе следует обратить внимание на то, что при сортировке производятся операции с адресами элементов массива, т. е. с указателями, а в самом исходном массиве элементы не сортируются. Возможный результат выполнения программы показан на рис. 8.2.
Задание 2 Выполните вывод отсортированного массива и исходного массива в два параллельных столбца. Напишите программу сортировки массива по возрастанию. Границы интервала равномерно распределенных случайных чисел: [–8; 2*X], где Х – номер компьютера, за которым выполняется лабораторная работа. Сгенерировать массив целых чисел размера N из интервала кодов строчных букв латинского алфавита, где N – число букв вашей фамилии. Из сформированного массива выведите коды полученных букв и сами буквы. Отсортируйте полученные буквы по возрастанию кодов составляющих букв с помощью указателей.Пример 3. Напишите программу заполнения матрицы по спирали натуральными числами с помощью массива указателей. Программный код решения примера: #include <stdio. h> #include <conio. h> #define n 15 int main(void) { int i = 1, I, j, k, p = n/2; int M[n][n], *ptr[n*n]; // Обнуление матрицы и инициализация указателя for (I = 0; I < n; ++I) for (j = 0; j < n; ++j) { M[I][j] = 0; ptr[I*n + j] = &M[I][j];} for (k = 1; k <= p; k++) // Число спиралей { // Верхний горизонтальный столбец for (j = (k-1); j < (n-k+1); j++) *ptr[(k-1)*n + j] = i++; // Правый верхний столбец for (j = k; j < (n-k+1); j++) *ptr[j*n + (n-k)] = i++; // Нижний горизонтальный столбец for (j = (n-k-1); j >= (k-1); --j) *ptr[(n-k)*n + j] = i++; // Левый верхний столбец for (j = (n-k-1); j >= k; j--) *ptr[j*n + (k-1)] = i++; } if ( n % 2 ) *ptr[p*n + p] = n*n; printf("\n\t Spiral matrix of dimention (%d x %d):\n\n",n, n); for (i = 0; i < n; ++i) for (j = 0; j < n; ++j) { if (n*n < 20*20){ printf("%4d", *ptr[i*n + j]); if (j == (n-1)) printf("\n");} else if (n*n >= 20*20) goto mes; } mes: if (n > 19) printf("\n\t It is a large matrix. Can not to see on display.\n"); printf("\n Press any key: "); _getch(); return 0; } Результат выполнения программы показан на рис. 8.3.
В программе использован оператор безусловного перехода goto, чтобы выйти из вложенных циклов, когда размерность матрицы велика и не может быть размещена на экране дисплея. Задание 3 Проверьте программу при размерности матрицы от 3 до 19 и более. Выведите на дисплей матрицу M[n][n]. Объясните результат. Дополните программу: создайте матрицу и заполните ее по спирали натуральными числами с помощью массива указателей, который использовался для взятия адресов элементов исходной матрицы М[n][n]. Объявите матрицу с начальной буквой вашей фамилии (с латинским написанием). Выведите на дисплей матрицу. Напишите программу заполнения матрицы по спирали и против часовой стрелки натуральными числами с помощью массива указателей. Дополните программу расчетом количества четных элементов выше главной диагонали матрицы и количества нечетных элементов ниже главной диагонали, не включая саму диагональ, с помощью указателей. Дополните программу подсчетом суммы элементов каждого столбца матрицы. Осуществите вывод результата на дисплей. Дополните программу подсчетом суммы элементов каждой строки матрицы. Осуществите вывод результата на дисплей. Дополните программу подсчетом суммы элементов диагонали матрицы. Осуществите вывод результата на дисплей.Примечание. Сумма диагональных элементов квадратной матрицы называется следом (шпуром) матрицы. Пример 4. Напишите программу заполнения целочисленной прямоугольной матрицы размером не более 15 Программный код решения примера: #include <stdio. h> #include <conio. h> #include <stdlib. h> #include <time. h> #define N 15 #define M 14 const int Left = -12; const int Right = 12; int main(void) { int i, j, n, m; int matr[N][M]; time_t t; //переменная системного времени srand((unsigned)time(&t)); printf("\n Enter the number of lines of the matrix is not more than %d: ", N); scanf_s("%d", &n); printf(" Enter the number of columns of the matrix is not more than %d: ", M); scanf_s("%d", &m); // Контроль ввода допустимой размерности if (n > N || m > M || n < 1 || m < 1) { printf("\n\t Data error! Repeat please.\n"); printf("\n Press any key: "); _getch(); return 0; } for (i = 0; i < n; ++i) for (j = 0; j < m; ++j) *(*(matr + i) + j) = 0; for (i = 0; i < n; ++i) for (j = 0; j < m; ++j) *(*(matr + i) + j) = (rand() % (2*Right+1)) + Left; printf("\n The matrix of random whole numbers from the entire [%d, %d]:\n", Left, Right); for (i = 0; i < n; ++i) { printf("\n "); for (j = 0; j < m; ++j) printf("%5d", *(*(matr + i) + j)); } printf("\n\n Press any key: "); _getch(); return 0; } В программе использованы спецификаторы const для объявления неизменяемых переменных. Функция srand() устанавливает начальное значение программного генератора псевдослучайных чисел, т. е. осуществляет рандомизацию последовательности псевдослучайных чисел. С этой целью определена системная переменная time(&t), которая практически всегда различна. Возможный результат выполнения программы показан на рис. 8.4.
Задание 4 Проверьте режим ошибочного ввода размерности матрицы. Осуществите перевод значений сформированной матрицы nПример 5. Напишите программу умножения целочисленных матриц с использованием указателей [8.5]. Условием перемножения двух матриц А и В является равенство числа столбцов матрицы А и числа строк матрицы В. Если первая матрица А имеет размер n
Поэлементное перемножение двух матриц в стандартной математической форме:
С учетом синтаксиса формирования массивов в языке С индексация должна начинаться с нуля, поэтому формулу перепишем в следующем виде:
Программный код решения примера: #include <stdio. h> #include <conio. h> #define n 7 #define k 12 #define m 4 int main(void) { int i, j, p; int A[n*k], B[k*m]; long int *C[n*m]; //Обнуление матриц for (i = 0; i < n; ++i) for(j = 0; j < m; ++j) C[i*m + j] = 0; for (i = 0; i < n; ++i) for (j = 0; j < k; ++j) A[i*k + j] = 0; for (i = 0; i < k; ++i) for (j = 0; j < m; ++j) B[i*m + j] = 0; // Заполнение матриц целыми числами for (i = 0; i < n; ++i) for (j = 0; j < k; ++j) A[i*k + j] = i + j - 6; for (i = 0; i < k; ++i) for (j = 0; j < m; ++j) B[i*m + j] = i + j - 11; // Циклы перемножения с накоплением суммы for (i = 0; i < n; ++i) for (j = 0; j < m; ++j) { C[i*m + j] = 0; for (p = 0; p < k; ++p) C[i*m + j] += (A[i*k + p] * B[p*m + j]); } printf("\n\t The result of multiplying the two matrices:\n\t\t C = A*B (%dx%d = %dx%d * %dx%d)\n", n, m, n, k, k, m); for (i = 0; i < n; ++i) { printf("\n\t"); for (j = 0; j < m; ++j) printf("%6ld", C[i*m + j]); } printf("\n\n Press any key: "); _getch(); return 0; } Обнуление матриц и массива указателей позволяет заранее выделить память. Для вывода результата перемножения предусмотрен спецификатор ld. Результат выполнения программы показан на рис. 8.5.
Задание 5 Промежуточный размер k примите за 5*Х, где Х – номер компьютера, за которым выполняется лабораторная работа. Предусмотрите корректный вывод результата перемножения двух матриц. Вывод результата перемножения выполните на основе операции разыменования. Напишите программу умножения матриц с обычной индексацией элементов массивов, т. е. без использования указателей. Включите нумерацию строк и столбцов (слева от матрицы и вверху над ней). Проверьте работу программы без предварительного обнуления.Пример 6. Напишите программу формирования трех массивов, состоящих из матриц размерностей 4 Для решения примера сформируем трехмерный массив размером 3 Программный код решения примера: #include <stdio. h> #include <conio. h> #define p 3 // Количество "подшитых" матриц #define n 4 // Число строк матрицы #define m 8 // Число столбцов матрицы int main (void){ int i, j, k, r; int *PTR[p*n*m], *M2[n*m]; // Заполнение одномерного массива for (i = 0; i < p*n*m; ++i) PTR[i] = (int*)(i + 1); printf("\n An array of dimension %dx%dx%d:\n", p, n, m); puts("==========================================="); // Разбивка массива for (k = 0; k < p; ++k){ printf("\n"); for (i = 0; i < n; ++i) { printf(" "); for (j = 0; j < m; ++j){ printf(" %3d ",PTR[k*n*m + i*m + j]); } printf("\n"); } } puts("==========================================="); printf("\n Select the number of arrays of a series of numbers (%d...%d): ",1, p); scanf_s("%d", &r); printf("\n Matrix number %d from three-dimensional array:\n", r); // Выбор матрицы из 3-мерного массива for (k = 0; k < p; ++k) for (i = 0; i < n; ++i) for (j = 0; j < m; ++j) if ( k == (r-1)) {M2[i*m + j] = PTR[k*n*m + i*m + j];} for (i = 0; i < n; ++i) { printf("\n "); for (j = 0; j < m; ++j) printf(" %3d ", M2[i*m + j]); } printf("\n\n Press any key: "); _getch(); return 0; } Трехмерный массив можно рассматривать как книгу с заданным количеством страниц (размер p = 3), размер которых определяется как матрица с заданным числом строк (размер n = 4) и с заданным количеством столбцов (размер m = 8). Страницы (матрицы) "сшиваются". С помощью указателя *PTR сначала формируется одномерный массив, а затем он разбивается на заданное количество матриц. В функциях puts() допускается перевод на новую строку с помощью символа форматирования \n. Результат выполнения программы показан на рис. 8.6.
Задание 6 В программу включите проверку вводимого номера матрицы, т. е. чтобы число rПример 7.Напишите программу поиска подстроки в строке, сформированной по случайному закону из 15 букв латинского алфавита, с помощью функции strstr(). В качестве подстроки принять первые три буквы своей фамилии. Для работы с функцией strstr() требуется подключение заголовочного файла string. h. Программный код решения примера: #include <stdio. h> #include <conio. h> #include <string. h> #include <stdlib. h> #include <time. h> int main (void) { int i, k = 0, n, in; int numSTR; int N = 1000; int numA, numZ; char str[16]; char sub[4]; char *ptr; srand((unsigned)time(NULL)); numA = (int)'a'; numZ = (int)'z'; printf("\n Enter the three letters: "); in = scanf_s("%s", sub, sizeof(sub)); if (in == 0) { printf("\n Error input. Press any key: "); _getch(); exit(1); } printf("\n\t substring is \"%s\"\n", sub); for (n = 0; n < N; n++) { for (i = 0; i < 15; i++) str[i] = numA + rand() % (numZ - numA) + 1; str[i] = '\0'; ptr = strstr(str, sub); if (ptr!= NULL) { numSTR = (int)(ptr - str +1); k++; break; } } if (k == 0) printf("\n\t Substring \"%s\" not found", sub); else printf("\n\t Substring \"%s\" found at positions %d, %d, %d", \ sub, numSTR, numSTR+1, numSTR+2); puts("\n"); for (i = 0; i < 15; i++) printf(" %3d)", i+1); puts(""); for (i = 0; i < 15; i++) printf(" %3c ", str[i]); printf("\n\n... Press any key: "); _getch(); return 0; } Подобный пример рассматривался в предыдущей работе. Видно, что применение указателя и функции strstr() существенно привело к уменьшению программного кода. Возможный положительный результат работы программы показан на рис. 8.7.
Задание 7 Объясните процесс вычисления правой части выражения с переменной numSTR. Примените вместо операторов цикла for операторы цикла while. Подсчитайте количество итераций формирования строки (из 15 символов), когда в ней будет найдена задаваемая подстрока (например, три латинские буквы вашей фамилии). Включите допустимо возможное для вашего компилятора количество итераций поиска подстроки в строке. Напишите программу имитации взлома пароля, состоящего из трех букв латинского алфавита. Если при трехкратном ручном вводе пароля, он не будет разгадан, то включите автоматический поиск.Контрольные вопросы Как рассматривает имя массива компилятор языка С? На какое место в памяти компьютера указывает имя массива? Какая связь между указателями и массивами в языке С? Как формируется массив указателей в языке С? Как следует организовать посимвольное формирование строки символов с помощью указателя? Как с помощью одного указателя произвести инициализацию и вывод результата на консоль двухмерного (трехмерного) числового массива? Как изменяется значение типизированного указателя при применении к нему операции адресного сложения? Как производится вычитание указателей? |
|
Программирование на языке C в Microsoft Visual Studio 2010 | |||||||||||
| |||||||||||
| |||||||||||
| |||||||||||
9. Лекция: Динамическое распределение памяти в языке С: В лекции рассматриваются вопросы динамического распределения памяти, Изучаются функции динамического распределения памяти и их применение для числовых и символьных массивов, для хранения данных. | |||||||||||
| |||||||||||
| |||||||||||
| |||||||||||
Теоретическая частьДинамическая память – это оперативная память компьютера, предоставляемая программе при ее работе. Динамическое размещение данных означает использование динамической памяти при работе программы. В отличие от этого статическое размещение (например, явное определение массива данных заданного типа) осуществляется компилятором в процессе компиляции (запуска) программы. Указатели используются для динамического выделения памяти компьютера для хранения данных [9.1]. Динамическое распределение означает, что программа выделяет память для данных во время выполнения. Память, выделяемая в С функциями динамического распределения данных, находится в так называемой динамически распределяемой области памяти (heap – куча) [9.1]. Динамически распределяемая область памяти – это свободная область памяти, не используемая программой, операционной системой или другими программами. Размер этой области памяти заранее неизвестен, но, как правило, в ней достаточно памяти для размещения данных программы. Хотя размер динамически распределяемой области памяти очень большой, все же она конечна и может быть исчерпана. Основу системы динамического распределения памяти в С составляют библиотечные функции calloc(), malloc(), realloc() и free() [9.1]. Рассмотрим прототипы этих функций. 1. Функция calloc() #include <stdlib. h> void *calloc(size_t num, size_t size); Функция calloc() выделяет память, размер которой равен значению выражения num * size, т. е. память, достаточную для размещения массива, содержащего num объектов размером size [9.1]. Выделенная область памяти обнуляется. Функция calloc() возвращает указатель на первый байт выделенной области памяти для массива num объектов, каждый из которых имеет размер size или NULL, если запрос на память выполнить нельзя [9.2]. Если для удовлетворения запроса нет достаточного объема памяти, возвращается нулевой указатель. Перед попыткой использовать распределенную память важно проверить, что возвращаемое значение не равно нулю. Тип void может быть переопределен для требуемого типа, т. е. для char, int, float, double. Пример фрагмента программного кода динамического распределения памяти для массивов заданного размера (например, вводится с клавиатуры): double *ptr; ptr = (double *) (calloc(10, sizeof(double))); if (!ptr) // условие логического отрицания {printf("Out of memory\n"); exit(1);} В приведенном примере число 10 – это размер одномерного массива с вещественными данными (типа double). В случае выделения памяти для двухмерного массива размера N*M строчка с функцией calloc() перепишется так: ptr = (double *) (calloc(N*M, sizeof(double))); При этом двухмерный массив рассматривается как аналог одномерного массива размера N*M. Использование явного приведения типов (double) сделано для того, чтобы обеспечить переносимость программы, в первую очередь для обеспечения совместимости с языком программирования С++. 2. Функция malloc() #include <stdlib. h> void *malloc(size_t size); Функция malloc() возвращает указатель на первый байт области памяти размера size, которая была выделена из динамически распределяемой области памяти [9.3]. Если для удовлетворения запроса в динамически распределяемой области памяти нет достаточного объема памяти, возвращается нулевой указатель NULL. При этом следует иметь в виду, что попытка использовать нулевой указатель обычно приводит к полному отказу системы. Выделенная область памяти не инициализируется [9.2]. Приведем фрагмент программного кода динамического распределения памяти для массивов заданного размера: double *ptr; ptr = (double *) (malloc(10*sizeof(double))); if (!ptr) // условие логического отрицания { // выход за пределы памяти printf("Out of memory. Press any key: "); _getch(); exit(1); } 3. Функция realloc() #include <stdlib. h> void *realloc(void *ptr, size_t size); В стандарте С89 функция realloc() изменяет размер блока ранее выделенной памяти, адресуемой указателем *ptr в соответствии с заданным размером size [9.1]. Значение параметра size может быть больше или меньше, чем перераспределяемая область. Функция realloc() возвращает указатель на блок памяти, поскольку не исключена необходимость перемещения этого блока. В этом случае содержимое старого блока (до size байтов) копируется в новый блок. Если новый размер памяти больше старого, дополнительное пространство не инициализируется [9.2]. Если запрос невыполним, то функция распределения памяти realloc() возвращает нулевой указатель NULL. Функция realloc() позволяет перераспределить ранее выделенную память. При этом новый размер массива может быть как меньше предыду щего, так и больше его. Если система выделит память в новом месте, то все предыдущие значения, к которым программа обращалась по указателю *ptr, будут переписаны на новое место автоматически. 4. Функция free() #include <stdlib. h> void free(void *ptr); Функция free() возвращает в динамически распределяемую область памяти блок памяти, адресуемый указателем *ptr, после чего эта память становится доступной для выделения в будущем [9.1]. Вызов функции free() должен вызываться только с указателем, который был ранее получен в результате вызова одной из функций динамического распределения памяти. Использование недопустимого указателя при вызове, скорее всего, приведет к разрушению механизма управления памятью и, возможно, вызовет крах системы [9.1]. Практическая частьПример 1. Напишите программу считывания строк разной длины с использованием массива указателей, когда строки вводятся с клавиатуры, и вывода считанных строк на дисплей. Программный код решения примера: #include <stdio. h> #include <conio. h> #include <stdlib. h> #define N 79 int main (void) { int i, m = 3; char *str[N+1]; char *str2[] = {"st", "nd", "rd"}; for (i = 0; i < m; ++i) str[i] = (char *) calloc((N+1), sizeof(char)); printf("\n Dynamic reading strings of different lengths\n\n"); for (i = 0; i < m; ++i) { if (str[i] == NULL) { printf("\n\t Error memory allocation.\n"); printf("\n Press any key: "); _getch(); exit(1); } printf("\t Enter %d%s string: ", i+1, str2[i]); gets_s(str[i], sizeof(str)/sizeof(char)); } printf("\n\t The strings are:\n"); for (i = 0; i < m; ++i) printf("\t %s\n",str[i]); printf("\n\n Press any key: "); _getch(); return 0; } Динамическое распределение памяти при каждом вводе новой строки осуществляется с помощью функции calloc(). Предусматривается проверка возвращаемого значения функции calloc(), которое не должно быть нулевым указателем, т. е. NULL. В функции gets_s() используется универсальное средство (sizeof(str)/sizeof(char)) определения размерности массива. Возможный результат выполнения программы показан на рис. 9.1.
Задание 1 Вывод символьного массива осуществите на основе его разыменования. Вместо функции calloc() примените функцию malloc() и введите (а потом выведите) свои фамилию, имя, номер группы, специальность (буквами латинского алфавита). Количество вводимых строк определите случайным образом из интервала целых чисел [3;7]. Видоизмените программу для ввода одной строки с несколькими словами различной длины (с различным количеством символов), а затем сформируйте массив строк из заданных слов. Предусмотрите вывод строк сформированного символьного массива. Отсортируйте символьный массив по убыванию длин введенных слов, считая, что прописные буквы имеют приоритет над строчными буквами. Сделайте вывод отсортированного массива на дисплей.Пример 2. Напишите программу для представления нижней треугольной матрицы, размер которой задается пользователем с клавиатуры и заполняется случайными равномерно распределенными числами из интервала [0; 12]. Квадратная матрица [ В случае прямоугольной матрицы размером n Программная реализация решения примера основывается на программе из [8]. Программный код решения примера: #include <stdio. h> #include <conio. h> #include <stdlib. h> #include <time. h> // Макрос #define READIn(VARIABLE) \ {printf("\n\t Enter the dimention of a square matrix (not exceeding 12):\n\t ");\ printf(#VARIABLE" = "); scanf_s("%d",&VARIABLE);} int main(void) { int i, j, nMatr, jRow; double **triMatr; time_t t; srand((unsigned int) time(&t)); READIn(nMatr); triMatr = (double **)calloc(nMatr, sizeof(double *)); for(i = 0; i < nMatr; i++) { jRow = (i < nMatr? i+1 : nMatr); triMatr[i]=(double *)calloc(jRow, sizeof(double)); for (j = 0; j < jRow; j++) triMatr[i][j] = (12*(double)rand()/RAND_MAX); } printf("\n Result (triangular matrix): "); for(i = 0; i < nMatr; i++) { printf("\n "); jRow = (i < nMatr? i+1 : nMatr); for (j = 0; j < jRow; j++) printf(" %5.2f",triMatr[i][j]); free (triMatr[i]); } free (triMatr); printf("\n\n Press any key: "); _getch(); return 0; } В программе применена препроцессорная директива #define... – макрос для ввода размерности матрицы. Применение макросов требует к себе внимания и осторожности. В программе применен указатель double **triMatr на массив указателей. В связи с этим определено двойное обращение к функции calloc(), с помощью которой выполняется динамическое распределение памяти. Следует обратить внимание также на то, что в программе дважды используется функция free() для освобождения выделенной памяти. Возможный результат выполнения программы показан на рис. 9.2.
Задание 2 Строки нижней треугольной матрицы заполните числами, равными номерам строк, т. е. число 1 – в первой строке, число 2 – во второй строке, число 3 – в третьей строке и т. д. Оператор условия? замените на другой оператор. Заполнение матрицы сделайте целыми случайными числами, равномерно распределенными из интервала [–2*Х; 2*Х], где Х – номер компьютера, за которым выполняется лабораторная работа. Напишите программу с динамическим формированием треугольной матрицы из данной прямоугольной матрицы. Напишите программу динамического формирования верхней треугольной матрицы. В приведенной программе предусмотрите вывод матрицы, в которой ниже главной диагонали (и сама диагональ) расположены случайные числа, а выше главной диагонали будут располагаться нули.Пример 3. Напишите программу заполнения одномерного массива случайными числами, распределенными по стандартному нормальному закону. Размерность массива вводится с клавиатуры пользователем. Для решения примера выберем метод Марсальи–Брея [9.3]. Его этапы: Генерируются два равномерно распределенных случайных числаПримечание. Нормальный закон характеризуется двумя параметрами: математическим ожиданием и среднеквадратическим отклонением (плюс корень квадратный из дисперсии), которые соответственно равны 0 и 1. Для оценки математического ожидания используется среднее значение данного объема n выборки случайных чисел. Для оценки дисперсии D могут быть использованы следующие формулы:
где m – среднее значение заданного массива. Программный код решения примера: #include <stdio. h> #include <conio. h> #include <stdlib. h> #include <time. h> #include <math. h> int main(void) { double *Norm, *Norm2; double R1, R2, z1, z2, V1, V2, S; int i, j, n; time_t t; srand((unsigned) time(&t)); printf("\n\t Enter the size of the array: "); scanf_s("%d", &n); // Выделение памяти для заданного массива данных Norm = (double *)malloc(n*sizeof(double)); Norm2 = (double *)malloc(n*sizeof(double)); // Реализация алгоритма метода Марсальи-Брея S = 1.0; for (i = j = 0; i < n; ++i, ++j){ while (S >= 1.0) { R1 = (double) rand()/RAND_MAX; R2 = (double) rand()/RAND_MAX; V1 = 2.0*R; V2 = 2.0*R; S = (V1*V1 + V2*V2); } z1 = V1 * sqrt(-2.0*log(S)/S); z2 = V2 * sqrt(-2.0*log(S)/S); Norm[i] = z1; Norm2[j] = z2; S = 1.0; } // Вывод нормально распределенных случайных чисел printf("\n\t Normally distributed random numbers:\n"); for(i = j = 0; i < n; i++, j++) {printf("\n\t %8.4f",Norm[i]);printf("\n\t %8.4f",Norm2[j]);} // Освобождение памяти free (Norm); free (Norm2); printf("\n\n Press any key: "); _getch(); return 0; } Возможный результат выполнения программы показан на рис. 9.3.
Задание 3 Рассчитайте минимальные и максимальные значения сформированных случайных чисел. Размерность массива случайных чисел примите за 100*Х, где Х – номер компьютера, за которым выполняется лабораторная работа. Вывод на экран дисплея не производите. С учетом пункта 2 рассчитайте среднее значение сформированного массива. Сравните с теоретическим значением. Напишите программу расчета массива нормально распределенных случайных чисел, для которых среднее значение отличается от теоретического математического ожидания на заданную величину (вводимую с клавиатуры). Первый вариант – за счет увеличения размерности массива, второй вариант – за счет увеличения числа прогона программы с заданной величиной размерности массива.Пример 4. Напишите программу заполнения одномерного символьного массива заданным числом (вводимого с клавиатуры) символов с добавлением символа восклицательного знака "!" в конце массива символов. Для решения примера используем функции динамического распределения памяти malloc() и realloc(). Программная реализация примера базируется на программе из [9.1]. Программный код решения примера: #include <stdio. h> #include <conio. h> #include <stdlib. h> #include <string. h> int main(void) { int n, m; char *ptr; // Размерность символьного массива printf("\n Enter a dimention of character array: "); scanf_s("%d", &n); _flushall(); // Выделение памяти для заданного объема символов ptr = (char *)malloc((n+1)*sizeof(char)); if (!ptr) { printf("\n\t 1st Error! "); printf("\n\n Press any key: "); _getch(); return -1; } // Ввод строки символов printf(" Enter a character array of no more than %d characters: ", n); gets_s(ptr, n+1); // Число символов в строке m = strlen(ptr); printf("\n Start line:\n"); printf(" %s\n", ptr); // Перераспределение памяти ptr = (char *)realloc(ptr, (m+2)*sizeof(char)); if (!ptr) { printf("\n\t 2nd Error! "); printf("\n\n Press any key: "); _getch(); return -1; } // Присоединение к массиву символов еще одного символа strcat_s(ptr, m+2, "!"); printf("\n Start line and character \"%c\":\n", '!'); printf(" %s\n", ptr); // Освобождение памяти free (ptr); printf("\n\n Press any key: "); _getch(); return 0; } В функции malloc() размер требуемой памяти делается с запасом на символ окончания строки, т. е. '\0'. Аналогично сделан запас количества символов для функции realloc(), так как функция strlen() возвращает количество символов в строке без нулевого символа. Функции gets_s() и strcat_s() определены в MS Visual Studio. В них предусматривается включение размерности символьных массивов, чего нет в стандартных функциях gets(), strcat() языка С. Возможный результат выполнения программы показан на рис. 9.4.
Размерность выделяемой памяти превышает число вводимых символов. Поэтому функция realloc() уменьшает размерность выделяемой памяти. Задание 4 Проверьте размерность итогового массива символов. Выведите сформированный массив символов в обратном порядке. Введите свою фамилию и свое имя через пробел. Подсчитайте количество вводимых символов и введите это число. Осуществите вывод массива символов с дополнительным случайным символом без применения функции strcat().Пример 5. Напишите программу транспонирования матрицы, размерности которой (количество строк и количество столбцов) вводятся с клавиатуры, а элементы – вещественные случайные числа, распределенные по равномерному закону из интервала [0;15]. По определению транспонированная матрица – это матрица Программный код решения примера: #include <stdio. h> #include <conio. h> #include <stdlib. h> #include <time. h> #include <locale. h> int main (void) { int i, j, n, m; double *A_ptr, *B_buf; // Для рандомизации псевдослучайных чисел srand((unsigned)time(NULL)); setlocale(LC_ALL, "Russian"); printf("\n Введите размерность матрицы - \n число строк и число столбцов через пробел: "); scanf_s("%d%d", &n, &m); A_ptr = (double *) calloc((n*m),sizeof(double)); B_buf = (double *) calloc((n*m),sizeof(double)); for (i = 0; i < n*m; ++i) A_ptr[i] = 15.0*rand()/RAND_MAX; setlocale(LC_NUMERIC, "English"); printf("\n Исходная матрица:\n"); for (i = 0; i < n; ++i) { printf("\n"); for(j = 0; j < m; ++j) printf(" %8.4f", A_ptr[i*m+j]); } // Основной фрагмент транспонирования for (i = 0; i < n; ++i) for (j = 0; j < m; ++j) B_buf[j*n+i] = A_ptr[i*m+j]; printf("\n\n Транспонированная матрица:\n"); for (j = 0; j < m; ++j) { printf("\n"); for(i = 0; i < n; ++i) printf(" %8.4f", B_buf[j*n+i]); } // Освобождение выделенной памяти free(A_ptr); free(B_buf); printf("\n\n Press any key: "); _getch(); return 0; } В программе использованы библиотечные функции для установки русских шрифтов setlocale(LC_ALL, "Russian") и вывода элементов матрицы с плавающей точкой: setlocale(LC_NUMERIC, "English"). Для этих функций подключен заголовочный файл locale. h. Возможный результат работы программы показан на рис. 9.5.
Задание 5 Подсчитайте число итераций циклов, необходимых для транспонирования. Выполните двойное транспонирование исходной матрицы. Выведите результат на консоль первого и второго транспонирования. Выполните решение примера с применением указателей на указатели. Заполните исходную матрицу натуральными числами начиная с номера компьютера, за которым выполняется лабораторная работа.Контрольные вопросыЧто такое динамическая память? Какие средства языка С используются для хранения данных с динамическим выделением памяти компьютера? Какие основные библиотечные функции языка С используются для динамического распределения памяти? Какое различие в действии функций malloc() и calloc()? Как осуществляется перераспределение динамической памяти? Для каких типов данных возможно динамическое распределение памяти? | |||||||||||
| |||||||||||
Программирование на языке C в Microsoft Visual Studio 2010 | |||||||||||
| |||||||||||
| |||||||||||
| |||||||||||
10. Лекция: Общие сведения о функциях языка С: | |||||||||||
| |||||||||||
| |||||||||||
| |||||||||||
Теоретическая часть Принципы программирования на языке С основаны на понятии функции. Например, к системным функциям относятся printf(), scanf(), gets(), putchar() и др. Функции – это строительные элементы языка С и то место, в котором выполняется вся работа программы [10.1]. Большие программы обычно состоят из нескольких пользовательских функций и ряда системных функций. Функция – самостоятельная единица программы. Функции повышают уровень модульности программы, облегчают ее чтение, внесение изменений и коррекцию ошибок. В основе всех программ на языке программирования С лежат одни и те же фундаментальные элементы – функции [10.2]. В частности, функция main() является обязательной для любой программы. Во всех программах С определяется единая внешняя функция с именем main(), служащая точкой входа в программу, то есть первой функцией, выполняемой после запуска программы [10.3]. Ни одна программа в языке С не может обойтись без функций. Функция в языке С играет ту же роль, что и подпрограммы или процедуры в других языках [10.4]. Каждая функция языка С имеет имя и список аргументов. По соглашению, принятому в языке С, при записи имени функции после него ставятся круглые скобки [10.4]. Это соглашение позволяет легко отличить имена переменных от имен функций. Рассмотрим модельный пример программы, в которой, кроме функции main(), содержатся еще три функции [10.4]. #include <stdio. h> int main(void) /* Главная функция */ { /* Начало тела функции */ function1(); /* вызов первой функции */ function2(); /* вызов второй функции */ function3(); /* вызов третьей функции */ } /* Конец тела функции main() */ /* Начало определения первой функции */ function1() { /* Начало тела первой функции */ /* Операторы первой функции */ /* Конец тела первой функции */ } /* Начало определения второй функции */ function2() { /* Начало тела второй функции*/ /* Операторы второй функции */ /* Конец тела второй функции*/ } /* Начало определения третьей функции */ function3() { /* Начало тела третьей функции*/ /* Операторы третьей функции */ /* Конец тела третьей функции*/ } В условной (модельной) программе имеются четыре функции: main(), function1(), function2(), function3(). Эти функции не имеют аргументов. Позднее рассмотрим функции, которые имеют аргументы. Аргументы функции – это величины, которые передаются функции во время ее вызова. Аргумент, стоящий в операторе вызова функции, называется фактическим параметром. Аргументы, стоящие в заголовке функции, называются формальными параметрами. В языке С функция может возвращать значение в вызывающую программу посредством оператора return. Оператор возврата из функции в точку вызова имеет две формы [10.5]: return; return выражение; В общем виде функция выглядит следующим образом [10.1]: возвр-тип имя-функции(список параметров) { Тело_функции } Тело_функции – это часть определения функции, ограниченная фигурными скобками и непосредственно размещенная вслед за заголовком функции. Тело функции может быть либо составным оператором, либо блоком [10.1]. В языке С определения функций не могут быть вложенными, т. е. внутри одной функции нельзя объявить и расписать тело другой функции. Возвращаемый тип возвр-тип функции определяет тип данного, возвращаемого функцией. Например, это могут быть int, float, double и т. д. В случае, когда функция ничего не возвращает, ей присваивается тип void. Функция может возвращать любой тип данных, за исключением массивов список параметров – это список, элементы которого отделяются друг от друга запятыми [10.1]. При вызове функции параметры принимают значения аргументов. Если функция без параметров, то такой пустой список можно указать в явном виде, поместив для этого внутри скобок ключевое слово void. Все параметры функции (входящие в список параметров) должны объявляться отдельно, причем для каждого из них надо указывать и тип, и имя. В общем виде список объявлений параметров должен выглядеть следующим образом [10.1]: fun(тип имя_перем1, тип имя_перем2,..., тип имя_перем N) Например: fun(int i, int j, float k, char str1, char str2) Рассмотрим пример программы с выводом сообщения не в главной функции main(), а в другой: #include <stdio. h> #include <conio. h> void printMessage (void) { printf("\n\t hello, world\n"); return; printf("\n\t 123\n"); } int main(void) { printMessage(); printf("\n Press any key: "); _getch(); return 0; } Результат выполнения программы показан на рис. 10.1.
Программа состоит из двух функций: printMessage() и main(). Выполнение программы всегда начинается с функции main(), которую называют еще главной. Внутри функции main() происходит вызов функции printMessage() без параметров. Когда происходит вызов функции, выполнение программы передается непосредственно вызванной функции. Внутри функции printMessage() выполняется только утверждение printf("\n\t hello, world\n"); Несмотря на то, что в функции printMessage() есть еще одно утверждение printf("\n\t 123\n"), которое не выполняется, поскольку используется утверждение возврата (return) из функции. В языке С функция введена как один из производных типов. Формальные параметры в определениях функций могут объявляться в форме прототипа [10.3]. Прототипы дают компилятору возможность тщательнее выполнять проверку типов аргументов [10.1]. Если используются прототипы, то компилятор может обнаружить любые сомнительные преобразования типов аргументов, необходимые при вызове функции, если тип ее параметров отличается от типов аргументов. Компилятор также обнаружит различия в количестве аргументов, использованных при вызове функции, и в количестве параметров функции. В общем случае прототип функции должен выглядеть таким образом [10.1]: тип имя_функции(тип имя_парам1, тип имя_парам2,..., тип им_парамN); В приведенной выше программе прототип функции printMessage() не использовался, так как сама функция была объявлена до главной функции main(). Для переносимости С-кода в С++ использование прототипа функции обязательно. Поэтому к хорошему стилю программирования относится использование прототипов функций, поскольку большие программы обычно состоят из нескольких функциях, часто расположенных в различных файлах. Вышеприведенная программа с использованием прототипа функции printMessage() будет выглядеть следующим образом: #include <stdio. h> #include <conio. h> //void printMessage (void);//Прототип функции int main(void) { void printMessage (void); //Прототип функции printMessage(); // Вызов функции printf("\n Press any key: "); _getch(); return 0; } // Определение функции void printMessage (void) { printf("\n\t hello, world\n"); return; printf("\n\t 123\n"); } В листинге Формальные параметры функции определены в прототипе функции. При обращении к функции используются фактические параметры, называемые аргументами функции. Список фактических параметров – это список выражений, количество которых равно количеству формальных параметров функции (исключение составляют функции с переменным числом параметров). Соответствие между формальными и фактическими параметрами устанавливается по их взаимному расположению в списках. Между формальными и фактическими параметрами должно быть соответствие по типам. Синтаксис языка С предусматривает только один способ передачи параметров – передачу по значениям. Это означает, что формальные параметры функции локализованы в ней, т. е. недоступны вне определения функции и никакие операции над формальными параметрами в теле функции не изменяют значений фактических параметров [10.4]. Передача параметров по значению предусматривает следующие шаги [10.4]: При компиляции функции выделяются участки памяти для формальных параметров, т. е. формальные параметры оказываются внутренними объектами функции. При этом для параметров типа float формируются объекты типа double, а для параметров типов char и short int создаются объекты типа int. Если параметром является массив, то формируется указатель на начало этого массива, и он служит представлением массива-параметра в теле функции. Вычисляются значения выражений, использованных в качесстве фактических параметров при вызове функции. Значения выражений – фактических параметров заносятся в участки памяти, выделенные для формальных параметров функции. В теле функции выполняется обработка с использованием значений внутренних объектов-параметров, и результат передается в точку вызова функции как возвращаемое ею значение. Никакого влияния на фактические параметры (на их значения) функция не оказывает. После выхода из функции освобождается память, выделенная для ее формальных параметров.Важным является момент, что объект вызывающей программы, использованный в качестве фактического параметра, не может быть изменен из тела функции. Для подобного изменения существует косвенная возможность изменять значения объектов вызывающей программы действиями в вызванной функции. Это становится возможным с помощью указателя (указателей), когда в вызываемую функцию передается адрес любого объекта из вызывающей программы. С помощью выполняемого в тексте функции разыменования указателя осуществляется доступ к адресуемому указателем объекту из вызывающей программы. Тем самым, не изменяя самого параметра (указатель-параметр постоянно содержит только адрес одного и того объекта), можно изменять объект вызывающей программы. Массивы и строки также могут быть параметрами функции. В этом случае внутрь функции передается только адрес начала массива. Тогда можно в качесстве параметра использовать указатель. Приведем два равноправных прототипа функций: float fun(int n, float A[ ], float B[ ]); float fun(int n, float *a, float *b); Поскольку массив передается в функцию как указатель, внутри функции можно изменять значения элементов массива–фактического параметра, определенного в вызывающей программе. Это возможно и при использовании индексирования, и при разыменовании указателей на элементы массива. В языке С существует возможность создавать функции, число аргументов которых не определено – функции с переменным числом аргументов [1]. При этом следует указать только количество аргументов. Пример прототипа функции с переменным числом аргументов: int fun(int n, ј); Многоточие (ј) в прототипе функции означает, что функция получает переменное число аргументов любого типа. Многоточие должно всегда находиться в конце списка параметров [1]. Макросы и определения заголовочного файла переменных аргументов stdarg. h (табл. 10.1) предоставляют программисту средства, необходимые для построения функций со списком аргументов переменной длины [1].
Примеры обращений к функции с фактическими аргументами: double k; double v1 = 1.5, v2 = 2.5, v3 = 3.5; // Первый вариант, где 3 – количество аргументов k = fun(3,v1, v2, v3); // Второй вариант, где 0.0 – завершающий нуль списка аргументов k = fun(v1, v2, v3, 0.0); Практическая часть Пример 1. Напишите программу сортировки по возрастанию заданного массива случайных чисел, равномерно распределенных в интервале [–6;6], с помощью вспомогательной функции. Программный код решения примера: #include <stdio. h> #include <conio. h> #include <stdlib. h> #include <time. h> #define MAX 10 // Прототип функции с формальными параметрами void sort(double arr[], int n); int main (void) { double M[MAX]; int i, size = MAX; long int L; unsigned int some; L = (long) time(NULL); srand((unsigned)L); for (i = 0; i < MAX; ++i) M[i] = 12.0*rand()/RAND_MAX - 6.0; printf("\n\t The original array:\n"); for (i = 0; i < MAX; ++i) printf("\t%8.4f\n", M[i]); // Обращение к функции с фактическими параметрами sort(M, size); // Распечатка отсортированного массива printf("\n\t After sorting: \n"); for (i = 0; i < MAX; ++i) printf("\t%8.4f\n", M[i]); printf("\n Press any key: "); _getch(); return 0; } // Вспомогательная функция сортировки void sort(double Array[], int m) { int i, j; double tmp; for (i = 0; i < m-1; ++i) for (j = 0; j < m-i-1; ++j) if (Array[j+1] < Array[j]) { tmp = Array[j]; Array[j] = Array[j+1]; Array[j+1] = tmp; } } Следует обратить внимание на имена формальных параметров в самой функции sort() и в ее прототипе: они имеют разные имена, но одинаковые типы. Фактические параметры или аргументы функции sort() в вызывающей программе (в теле функции main()) имеют свои имена, не связанные с именами формальных параметров. Заполнение массива случайными числами производится с помощью библиотечной функции rand() и макроопределения RAND_MAX. Для рандомизации массива случайных чисел при каждом новом запуске программы используется библиотечная функция srand(), аргументом которой является системное время, формируемое библиотечной функцией time(). Возможный результат выполнения программы показан на рис. 10.2.
Задание 1 Используйте программу без прототипа функции sort(). С помощью вспомогательной функции произведите сортировку массива по убыванию. С помощью вспомогательной функции произведите сортировку целых чисел из заданного интервала [X; 10*X], где Х – номер компьютера, за которым выполняется лабораторная работа. Выполните операцию умножения исходного массива как матрицы-столбца на отсортированный массив как матрицы-строки. Выведите на консоль результат перемножения. Для операции перемножения создайте специальную функцию. Выполните операцию умножения исходного массива как матрицы-строки на отсортированный массив как матрицы-столбца. Выведите на консоль результат перемножения. Для операции перемножения создайте специальную функцию.Пример 2. Напишите программу вычисления квадратного корня числа по методу Ньютона–Рафсона с использованием функции расчета квадратного корня числа и функции расчета абсолютного значения числа. При вычислении квадратного корня из числа следует помнить, что подкоренное выражение не должно быть отрицательным. Алгоритм метода Ньютона–Рафсона для вычисления квадратного корня числа: Шаг 1. Выбрать приблизительное значение 1. Шаг 2. Если |guess^2 – x| < e, перейти к шагу 4. Шаг 3. Установить приблизительное значение, равное (x/guess + guess)/2, и перейти к шагу 2. Шаг 4. Считать приблизительное значение квадратным корнем числа [7]. Программный код решения примера: #include <stdio. h> #include <conio. h> // Функция абсолютного значения числа double absValue(double x) { if (x < 0) x = - x; return (x); } // Функция расчета квадратного корня из числа double squareRoot (double x) { const double epsilon = 0.000001; double guess = 1.0; //Начальное приближение while (absValue(guess*guess - x) >= epsilon) guess = (x/guess + guess)/2.0; return (guess); } int main (void) { double result, X; printf("\n\t Enter a number: "); scanf_s("%lf", &X); // Обращение к функции с фактическим параметром result = squareRoot(X); printf("\n\t Square root of \"%1.4f\" is: %1.8f\n", X, result); printf("\n Press any key: "); _getch(); return 0; } В программе используются три функции: main(), absValue(), squareRoot(). Вспомогательные функции расположены в определенном порядке: сначала функция absValue(), а потом функция squareRoot(). В функцию absValue() передается значение guess, которое вычисляется в функции squareRoot(), находящейся в условии оператора цикла while. Когда условие для оператора цикла будет ложным, т. е. когда значение корня будет меньше заданного числа epsilon, то полученное значение возвращается в вызывающую функцию main(). В программе использована переменная epsilon, определенная с помощью спецификатора const, что делает переменную неизменной. При таком объявлении компилятор определяет ее как константное значение. Таким переменным нельзя присваивать значения в программе, нельзя их инкрементировать или декрементировать [10.2]. Возможный результат выполнения программы показан на рис. 10.3.
Задание 2 В качестве формальных параметров функций возьмите начальные буквы своей фамилии, имени из двух букв. В качестве возвращаемого значения из функции squareRoot() – месяц своего рождения. В функции absValue() примените оператор условия ? вместо оператора if без определения дополнительной переменной. В главной функции main() введите проверку ввода неотрицательного числа. В программу введите прототипы функций. Зафиксируйте результат вычисления квадратного корня от числа Х, где Х – номер компьютера, за которым выполняется лабораторная работа. В качестве начального приближения возьмите 1.5*Х, где Х – номер компьютера, за которым выполняется лабораторная работа. В программе примените тип float вместо типа double.Пример 3. Напишите программу поиска максимального элемента среди минимальных элементов строк двухмерного целочисленного массива. Условие примера соответствует поиску максимина в двухмерном массиве, т. е.
где Алгоритм поиска максмина заключается в следующем [10.6]. Сначала предполагаем, что максимальным элементом является начальный элемент первой строки A[0][0], затем заменяем его минимальным элементом этой же строки, т. е. теперь минимальный элемент первой строки принимается за искомый максимум. Последовательно просматривая остальные строки, находим в каждой из них минимальный элемент. Если окажется, что минимальный элемент текущей строки больше текущего максимума, то он принимается за максимальный. Результатом поиска являются значение максимального элемента и его индексы (номер строки и номер столбца). Программный код решения примера: #include <stdio. h> #include <conio. h> #include <stdlib. h> #include <time. h> #define n 6 #define m 7 const int N = 100; int main (void) { //Прототип функции int MaxMin(int A[][m], int nn, int mm, int *imax, int *jmax); int i, j, A[n][m], max, imax, jmax; long int L; L = (long) time(NULL); srand((unsigned)L); //Заполнение матрицы целыми случайными числами for (i = 0; i < n; ++i) for (j = 0; j < m; ++j) A[i][j] = 2*N*rand()/RAND_MAX - N; // Распечатка матрицы printf("\n\t The original matrix A(%d*%d):\n\n", n, m); for (i = 0; i < n; ++i){ printf("\t"); for (j = 0; j < m; ++j) printf("%4d", A[i][j]); printf("\n"); } max = MaxMin(A, n, m, &imax, &jmax); printf("\n\t Result: MaxMin = A[%d][%d] = %d\n", imax+1, jmax+1, max); printf("\n Press any key: "); _getch(); return 0; } // Функция поиска максмина int MaxMin(int A[][m], int nn, int mm, int *imax, int *jmax) { int i, j, min, max, imin, jmin; max = A[0][0]; *imax = 0; *jmax = 0; for (j = 1; j < mm; ++j) if (A[0][j] < max) {max = A[0][j]; *imax = 0; *jmax = j;} for (i = 1; i < nn; ++i) { min = A[i][0]; imin = i; jmin = 0; for (j = 1; j < mm; ++j) if (A[i][j] < min) {min = A[i][j]; imin = i; jmin = j;} if (max < min) {max = min; *imax = imin; *jmax = jmin;} } return max; } В программе поиск максмина выполняется на основе обычной индексации двухмерного массива. В объявлении функции MaxMin() для матрицы явно указывается только второй размер – количество столбцов. Указатели *imax, *jmax используются для передачи в вызывающую функцию индексов найденного максимина. Возвращение самого максимина осуществляется по значению с помощью оператора return, т. е. return max. Вывод значений индексов максмина сделан традиционно в соответствии с принятой обычной математической индексацией. Возможный результат выполнения программы показан на рис. 10.4.
Задание 3 Подсчитайте число итераций, используемых при поиске максмина. В объявлении матрицы MaxMin() (и ее прототипа) примените указатель на одномерный массив, например, int *a вместо int A[ ][m]. Число строк матрицы задайте как 3*Х, где Х – номер компьютера, за которым выполняется лабораторная работа. Размерность анализируемого массива (матрицы) задайте с клавиатуры. С клавиатуры задайте также интервал генерирования случайных чисел функцией rand() в виде [–2*Х; 2*Х], где Х – номер компьютера, за которым выполняется лабораторная работа. Напишите функцию определения субмаксмина, т. е. максимального числа среди минимальных чисел строк двухмерного массива после найденного максимина. Определите абсолютные значения максимума и минимума сформированной матрицы, для которой находится максимин.Пример 4. Напишите программу поиска одного элемента в линейной неупорядоченной таблице по совпадению ключа на основе заграждающего элемента. Таблицу опишите в виде одномерного массива целых чисел [10.6]. В линейной таблице элементы располагаются друг за другом, т. е. для каждого элемента таблицы существуют отношения порядка [10.6]. Линейные таблицы в оперативной памяти компьютера отображаются в массивы или линейные связанные списки. Поиск одного элемента в неупорядоченной таблице по заданному условию осуществляется последовательным просмотром элементов или до нахождения искомого элемента, т. е. до выполнения условия поиска, или до конца таблицы, если искомый элемент не найден. Возвращаемыми значениями являются адрес (индекс) элемента, или значение элемента, либо признак отсутствия элемента [10.6]. В случае применения заграждающего элемента последняя запись таблицы запоминается, а после завершения поиска восстанавливается в таблице. В последний элемент массива (когда таблица представляется в виде одномерного массива данных) заносится ключ поиска, и образуется так называемый заграждающий элемент. Теперь на каждом шаге поиска осуществляется только одно сравнение, а сам поиск продолжается до нахождения элемента с заданным ключом. Если искомого элемента в исходной таблице не было, то поиск закончится на заграждающем элементе. Использование заграждающего элемента в случае числовых ключей существенно сокращает количество сравнений. Программный код решения примера: #include <stdio. h> #include <conio. h> #define size 10 // Таблица числовых элементов int A[size] = {1,-7,3,4,8,-5,-2,6,0,9}; int main (void) { int i, ind, key; int search(int A[], int n, int key); printf("\n\t The original array:\n\t"); for (i = 0; i < size; ++i) printf("%3d", A[i]); // Поиск ключа соответствия printf("\n\n\t Search for key matches.\n"); // Ввод ключа поиска printf("\t Key in your search: "); scanf_s("%i", &key);; ind = search(A, size, key); if (ind == -1) printf("\n\t Element \"%d\" could not be found\n", key); else printf("\n\t Element \"%d\" index is %d\n", key, ind+1); printf("\n Press any key: "); _getch(); return 0; } int search(int A[], int n, int key) { int i = 0, r; r = A[n-1]; A[n-1] = key;// Заграждающий элемент while (A[i] != key) i++; A[n-1] = r; //Восстановление последнего элемента if ((i == n-1) && (r!= key)) return -1; // Отсутствие элемента elseF return i; // Успешный поиск } В программе использовано внешнее объявление одномерного массива с инициализацией. Термин "внешний" здесь используется, чтобы подчеркнуть расположение объявление вне функций; напрямую с ключевым словом extern он не связан [10.4]. Возможный результат выполнения программы показан на рис. 10.5.
Задание 4 Подсчитайте число итераций при поиске элемента по заданному ключу. Напишите программу без заграждающего элемента. Подсчитайте число итераций при поиске элемента по заданному ключу. Сравните с числом итераций при поиске с заграждающим элементом. Сформируйте динамический одномерный массив размерностью 12*Х, и заполните его целыми случайными числами, распределенными равномерно из интервала [0; 20*X], где Х – номер компьютера, за которым выполняется лабораторная работа. Ввод ключа для поиска выполните с клавиатуры. Поиск выполните с заграждающим элементом. С учетом предыдущего пункта задания ввод ключа задайте случайным образом. Если по этому ключу элемент не найден, то программным путем организовать повторный поиск с заданием нового случайного ключа. В случае, если при 5*Х-кратном поиске элемент не найден, вывести сообщение об отсутствии искомого элемента по всем заданным ключам. Поиск выполнить с заграждающим элементом.Пример 5. Напишите программу выделения слов из символьной строки, когда слова в ней разделены пробелами, и поместите каждое слово в отдельной строке свободного (вспомогательного массива) [10.4]. Программный код решения примера: #define _CRT_SECURE_NO_WARNINGS #include <stdio. h> #include <conio. h> #include <stdlib. h> #include <string. h> #define N 123 #define M 40 int wordstr(char *c, char **FA) { int Ln, n = 0, i = 0; char *wr; char *tempstr; Ln = strlen(c); tempstr = (char *)calloc(Ln + 1, sizeof(char)); strcpy(tempstr, c); wr = strtok(tempstr, " "); Ln = strlen(wr); FA[i] = (char *)calloc(Ln + 1, sizeof(char)); strcpy(FA[i++], wr); n++; while (wr = strtok(NULL, " ")) { Ln = strlen(wr); FA[i] = (char *) calloc(Ln + 1, sizeof(char)); strcpy(FA[i++], wr); n++; } free(tempstr); return n; } int main (void) { int i, n; char *Farr[M]; char str[N]; printf("\n\t Enter the string of characters:\n >> "); gets_s(str, N-1); printf("\n\t The original string of characters:\n"); printf(" %s\n", str); //Обращение к функции обработки строки n = wordstr(str, Farr); //Распечатка слов, помещенных в свободный массив printf("\n\t The characters in a free array:"); for (i = 0; i < n; ++i) printf("\n\t %s", Farr[i]); //Освобождение памяти, занятую выделенными словами for (i = 0; i < n; ++i) {free(Farr[i]); Farr[i] = NULL;} printf("\n\n Press any key: "); _getch(); return 0; } В программе использована библиотечная функция strtok(), которая выделяет слова из строки. При этом для каждого выделенного слова получаем динамическую память с помощью функции calloc(). Адрес выделенной памяти помещаем в соответствующий указатель массива свободных строк FA[]. Обратите внимание, что в функции calloc() вводится число на единицу больше, чем длина строки. Это сделано для учета символа завершения строки (или одного слова), т. е. для символа "\0" Для исключения предупреждений о безопасной работе с функциями strcpy(), strtok() в MS Visual Studio в программе используется директива #define _CRT_SECURE_NO_WARNINGS. Результат выполнения программы показан на рис. 10.6.
Задание 5 В программу включите обработку символов запятая ",", точки с запятой ";" и точки "." в качестве разделителей слов. Введите в строку свою фамилию, имя, год рождения, место рождения, специальность, на которой вы обучаетесь, номер группы и курс обучения. Выполните вывод на консоль. С учетом предыдущего пункта выполните выделение фамилии и курс обучения. В программу включите прототип функции wordstr().Пример 6. Написать программу расчета суммы и среднего арифметического произвольного количества данных. В качестве ключевых слов для выбора варианта расчета принять mean (среднее) и sum (сумма). В данном примере необходимо использовать функцию с переменным числом аргументов. Формула расчета среднего арифметического (m):
где Программный код решения примера #include <conio. h> #include <string. h> #include <stdarg. h> #include <float. h> /* Прототип функции с переменным числом аргументов*/ double varfun(char str[], double v1, ...); int main(void) { double v1 = 10.0, v2 = 2.5, control; char str[121]; printf("\n Enter one of the key words 'mean' or 'sum': "); gets_s(str, 120); control = varfun(str, v1, v2, 7.5, 0.0); if (control < DBL_MAX) printf("\n Result: %lf\n", control); else printf("\n Invalid input keyword.\n"); printf("\n\n... Press any key: "); _getch(); return 0; } /* Определение функции с переменным числом аргументов */ double varfun( char str[], double v1, ...) { /* Указатель на переменные списка аргументов */ va_list parg; double sum = v1; double value = 0.0; /* значение аргумента */ int count = 1; /* начальное количество аргументов */ int k, p; char *ch = "mean"; char *ch2 = "sum"; // Лексиграфическое сравнение строк k = strcmp(str, ch); if ( !k ) // k == 0 p = 1; else if ( k ) // k!= 0 { k = strcmp(str, ch2); if ( !k ) p = 2; else p = 0; } if ( p == 1 ) { va_start(parg, v1); /* инициализация указателя parg */ /* Просмотр списка аргументов*/ while( (value = va_arg(parg, double)) != 0.0) { // Суммирование числовых аргументов функции varfun() sum += value; count++; } /* Завершение процесса счтывания аргументов */ va_end(parg); return sum/count; } if ( p == 2 ) { va_start(parg, v1); while( (value = va_arg(parg, double)) != 0.0) { sum += value; count++; } va_end(parg); return sum; } return DBL_MAX; } Возможный вариант выполнения программы показан на рис. 10.7.
Задание 6 Напишите функцию с явным указанием количества аргументов. Включите в программу расчет исправленной выборочной дисперсии (D), которая рассчитывается по формуле.
где Контрольные вопросы Что лежит в основе всех программ, созданных на языке С? Какие типы данных может возвращать функция? И что не может возвращать? Что такое прототип функции? Какие элементы объявления функции входят в ее прототип? В чем разница между фактическими и формальными параметрами функции? Какой способ передачи параметров в функциях предусматривает синтаксис языка С? Как можно изменить значение аргумента функции в теле самой функции? Какая область видимости переменных, определенных в теле функции? Можно ли использовать функцию без параметров и без служебного слова void? К каким последствиям это может привести? Как следует Напишите программу, состоящую из нескольких пользовательских функций, без прототипов созданных функций? | |||||||||||
|
Программирование на языке C в Microsoft Visual Studio 2010 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
11. Лекция: Указатели и функции в языке программирования С: версия для печати и PDA | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Теоретическая частьВ предыдущей лабораторной работе рассматривались примеры функций, аргументами которых выступали указатели. В данной лабораторной работе подробнее будут рассмотрены вопросы, касающиеся указателей и функций. Ранее было отмечено, что в языке С аргументы передаются в функции по значению и не существует прямого способа изменить переменную вызывающей функции, действуя внутри вызываемой функции. Благодаря аргументам-указателям функция может обращаться к объектам в вызвавшей ее функции, в том числе модифицировать их [11.1]. В качестве примера рассмотрим функцию swap(), в задачу которой входит обмен элементов местами. Для решения такой задачи необходимо передать из вызывающей программы (например, из главной функции main()) в функцию указатели на переменные, которые нужно изменить. Программный код решения примера: #include <stdio. h> #include <conio. h> // Прототип функции void swap(int*, int*); int main (void) { int a = 10, b = -20; // Вывод на консоль исходных значений переменных printf("\n Initial values:\n a = %d, b = %d\n", a, b); // Вызов функции swap() с фактическими параметрами swap(&a, &b); // Результат после обращения функции swap() printf("\n New values:\n a = %d, b = %d\n", a, b); printf("\n... Press any key: "); _getch(); return 0; } // Определение функции void swap(int *pa, int *pb) { int temp; temp = *pa; *pa = *pb; *pb = temp; } В программе в качестве фактических параметров функции swap() выступают адреса заданных переменных. Можно было в главной функции определить указатели и инициализировать их адресами заданных переменных, а потом передать эти указатели в функцию swap. Результат выполнения программы показан нa рис. 11.1.
Указатели, передаваемые в функцию, могут быть указателями на указатели. Указатели могут указывать на начало какого-либо массива и т. д. Указатели могут использоваться для защиты массивов, над которыми необходимо произвести некоторые вычисления или преобразования. Особым свойством указателей можно считать возможность использовать их в качестве возвращаемых значений функций. Поскольку функции возвращают только одно значение, то несколько значений одного типа можно поместить в массив, а затем указатель на этот массив использовать в качестве возвращаемого значения. Общая форма определения функции, которая возвращает указатель, следующая: тип *имя_функции ( аргументы функции ) { // тело функции тип *имя_указателя; ? return имя_указателя; } Рассмотрим пример, в котором осуществляется сложение двух одномерных массивов и результат возвращается через указатель. Программный код решения примера: #include <stdio. h> #include <conio. h> #include <stdlib. h> int *out2(int A[], int B[], int); int main (void) { int i, n; int A[] = {1,2,3,4,5}; int B[] = {2,2,2,2,2}; int *ptrAB = NULL; n = (sizeof(A)/sizeof(A[0])); puts("\n The initial arrays: "); for (i = 0; i < n; i++) printf(" %d", A[i]); puts(""); for (i = 0; i < n; i++) printf(" %d", B[i]); ptrAB = out2(A, B, n); puts("\n\n Result from function: "); for (i = 0; i < n; i++) printf(" %d", ptrAB[i]); puts("\n\n Control of the arrays: "); for (i = 0; i < n; i++) printf(" %d", A[i]); puts(""); for (i = 0; i < n; i++) printf(" %d", B[i]); free(ptrAB); // освобождение выделенной памяти printf("\n\n... Press any key: "); _getch(); return 0; } int *out2(int A[], int B[], int n) { int i; int *ptr = (int *)calloc(n, sizeof(int)); //выделение памяти for (i = 0; i < n; i++) ptr[i] = A[i] + B[i]; return ptr; } Программа не требует особых пояснений. Следует отметить, что никогда не следует возвращать адрес переменной, определенной в теле функции, так как переменные функции являются локальными, и они существуют только во время работы функции. Указатели возвращаются подобно значениям любых других типов данных. Чтобы вернуть указатель, функция должна объявить его тип в качестве типа возвращаемого значения. Таким образом, если функция возвращает указатель, то значение, используемое в ее инструкции return, также должно быть указателем. В частности, многие библиотечные функции, предназначенные для обработки строк, возвращают указатели на символы. В языке С существует такой механизм как указатель на функцию. Допустим, существует несколько функций для различных операций с данными. В этом случае оказывается удобным определить указатель на функцию, и использовать его там, где требуется производить расчет для различных функций. Указатель на функцию – это переменная, содержащая адрес в памяти, по которому расположена функция [11.2]. Имя функции – это адрес начала программного кода функции. Указатели на функции могут быть переданы функциям в качестве аргументов, могут возвращаться функциями, сохраняться в массивах и присваиваться другим указателям на функции [11.2]. Типичное определение указателя на функцию следующее: тип_возвращаемый_функцией(*имя_указателя_на_функцию)(аргументы); В приведенном объявлении используются круглые скобки, в которых собственно и определяется указатель на функцию, которая возвращает тот или иной тип – тип_возвращаемый_функцией. Хотя знак * обозначает префиксную операцию, он имеет более низкий приоритет, чем функциональные круглые функции, поэтому для правильного комбинирования частей объявления необходимы еще и дополнительные скобки [11.1]. При этом аргументы – это аргументы той или иной функции с заданным типом возвращаемого значения, и на которую ссылается указатель *имя_указателя_на_функцию. Очевидно, что возможны сложные объявлений функций. Указатели на функции часто используются в системах, управляемых меню [11.2]. Пользователь выбирает команду меню (одну из нескольких). Каждая команда обслуживается своей функцией. Указатели на каждую функцию находятся в массиве указателей. Выбор пользователя служит индексом, по которому из массива выбирается указатель на нужную функцию. Другим типичным применением указателей на функции являются реализация обобщенных алгоритмов, например, алгоритмов сортировки и поиска. В этом случае критерии сортировки и поиска реализуются в виде отдельных функций и передаются при помощи указателей на функции в качестве параметра реализации основного алгоритма. Практическая частьПример 1. Напишите программу с функцией пузырьковой сортировки, использующей вызов по ссылке. В условии примера "вызов по ссылке" означает, что в качестве фактических параметров функций будут использоваться адреса переменных. И в этом случае прототип таких функций будет содержать указатели на соответствующие типы. Программный код решения примера: #include <stdio. h> #include <conio. h> // Прототип функции void bsort (int* const, const int); int main (void) { int A[] = {56, 34, 2, 0, 1, -21, 6, 8, 7}; int i, n; //Размерность массива n = sizeof(A)/sizeof(A[0]); puts("\n Data items in original order:"); for (i = 0; i < n; i++) printf(" %3d", A[i]); // Вызов функции сортировки - bsort() bsort (A, n); puts("\n\n Data items in ascending order:"); for (i = 0; i < n; i++) printf(" %3d", A[i]); printf("\n\n... Press any key: "); _getch(); return 0; } // Определение функции void swap(int *pa, int *pb) { int temp; temp = *pa; *pa = *pb; *pb = temp; } void bsort (int *const arr, const int size) { int pass, //счетчик проходов j; // счетчик сравнений // Прототип функции обмена - swap() void swap (int*, int*); // Цикл для контроля проходов for (pass = 0; pass < size - 1; pass++ ) { // цикл для контроля сравнений на данном проходе for (j = 0; j < size - 1; j++) { // обмен значений при нарушении порядка возрастания if (arr[j] > arr[j + 1]) { swap(&arr[j], &arr[j+1]); } } } } В программе функция сортировки bsort() в качестве формального параметра используется константный указатель, который указывает на первый элемент заданного массива. Второй формальный параметр также константный, чтобы подчеркнуть неизменность этого параметра в теле функции bsort(). Передача функции размера массива в качестве параметра имеет два преимущества – это хороший стиль программирования и, кроме того, такую функцию можно использовать многократно. Прототип функции swap() включен в тело функции bsort(), потому что это единственная функция, которая вызывает функцию обмена swap(). Пример выполнения программы показан на рис. 11.2.
Задание 1 Напишите программу сортировки вещественных чисел в количестве семи, которые должны быть случайными по равномерному закону из интервала от –Х до +Х, где Х – номер компьютера, за которым выполняется лабораторная работа. Видоизмените программу так, чтобы функция bsort() возвращала указатель на отсортированный массив, а сам исходный массив был при этом неизменным. Предусмотрите вывод на консоль исходного массива, потом отсортированного массива после вызова функции сортировки, и снова для контроля исходный массив. При этом аргументы функции bsort() оставить без изменения.Пример 2. Напишите программу, в которой используется функция по расчету среднего значения (среднего арифметического) одномерного числового массива, его исправленной выборочной дисперсии и среднего квадратичного отклонения (стандартного отклонения). Эти значения должны быть выведены на консоль в главной функции программы main() Приведем формулы, по которым рассчитываются среднее значение выборки, исправленная выборочная дисперсия и среднеквадратичное отклонение. Среднее выборочное значение одномерного массива размерностью N
где – элементы массива. Исправленная выборочная дисперсия одномерного массива размерностью N
где mean – среднее значение данного массива Исправленное среднеквадратичное отклонение S
где D – дисперсия данного массива. Программный код решения примера: #include <stdio. h> #include <conio. h> #include <stdlib. h> #include <math. h> // Прототип функции double *mean_D_S(int arr[], int n); int main(void) { int mass[] = {2, -3, 5, 6, 7, 8, 9,-1}; int i, n; double *R; n = sizeof(mass)/sizeof(mass[0]); puts("\n\t Initial array:"); for (i = 0; i < n; i++) printf(" %3d", mass[i]); // Вызов функции R = mean_D_S(mass, n); // Вывод расчтных характеристик массива printf("\n\n The average value of an array: %g\n", R[0]); printf(" The dispersion of the array: %g\n", R[1]); printf(" The standard deviation of the array: %g\n", R[2]); // Освобождение памяти free(R); printf("\n... Press any key: "); _getch(); return 0; } // Определение функции double *mean_D_S(int arr[], int N) { int j; double *PTR3 = (double *)calloc(N, sizeof(double)); double mean, D, S; mean = 0.0; for (j = 0; j < N; j++) mean += arr[j]; mean /= N; D = 0.0; for (j = 0; j < N; j++) D += (arr[j] - mean)*(arr[j] - mean); D /= (N-1); S = sqrt(D); PTR3[0] = mean; PTR3[1] = D; PTR3[2] = S; return PTR3; } В программе используется функция динамического распределения памяти calloc(), которая выделенные ячейки памяти обнуляет. Расчетные характеристики одномерного массива размещаются последовательно друг за другом в выделенной памяти для указателя *PTR3. Сформированный указатель функция возвращает в точку вызова функции mean_D_S(). Результат выполнения программы показан на рис. 11.3.
Задание 2 В качестве первого аргумента функции mean_D_S() используйте указатель на числовой массив. Воспользуйтесь справкой по математическим функциям и в программе примените функцию, которая осуществляет возведение в степень. Дополните возврат функцией mean_D_S() еще исходного массива, поэлементно возведенного в квадрат. В главной функции main() результаты выведите на консоль. Вместо типа double используйте тип float.Пример 3. Напишите программу с указателем на функции, которые рассчитывают следующие статистические характеристики одномерного числового массива: среднее арифметическое значение, медиану и модус (моду). Среднее арифметическое рассчитывалось в предыдущем примере. Приведем определения медианы и модуса, взятые из книги [11.3], из которой также взяты основные фрагменты программных кодов (в книге программы написаны на С++). Медиана – это серединное значение в наборе данных – т. е. такое, что ровно половина значений располагается выше, и ровно половина ниже его. Модус – это значение, наиболее часто встречающееся в наборе данных. Программный код решения примера: #include <stdio. h> #include <conio. h> double mean(int*, int); double median(int*, int); double mode(int*, int); int main(void) { int mass[] = {5, 6, 5, 5, 3, 6, 5, 3, 1, 4, 5, 3, 1, 6, 5, 2, 5, 2, 3, 4}; int *ptr = mass; int temp, i, j, k, n; // Указатель на функции double (*fun[3])(int*, int) = {mean, median, mode}; n = sizeof(mass)/sizeof(mass[0]); puts("\n The original array:"); j = 0; for (i = 0; i < n; i++) { j++; if ( j%6 ) printf(" %2d", mass[i]); else {puts(""); printf(" %2d", mass[i]); j = 1;} } // Сортировка методом выбора for (i = 0; i < (n - 1); ++i) { temp = ptr[i]; k = i; for (j = i + 1; j < n; ++j) if (ptr[j] < temp) { k = j; temp = ptr[k]; } ptr[k] = ptr[i]; ptr[i] = temp; } puts("\n\n Results - mean, mediana, modus: "); for (i = 0; i < 3; i++) printf("%6g\n",(*fun[i])(ptr, n)); printf("\n\n... Press any key: "); _getch(); return 0; } // Определения функций double mean(int* arr, int N) { int i; double aver = 0.0; for (i = 0; i < N; i++) aver += arr[i]; return (aver/N); } double median(int* arr, int N) { int i; double med = 0.0; for (i = 0; (i < N/2); i++) med = arr[i]; if ( N % 2) med = arr[i]; else med = (med + arr[i])/2; return med; } double mode(int* arr, int N) { int instances = 0, tempinst = 1, i = 1; double tempmode, mode_return = 1.0; tempmode = (double)arr[0]; while (i < N) { while ((double)arr[i] == tempmode ) { i++; tempinst++; } if (tempinst > instances) { mode_return = tempmode; instances = tempinst; } tempinst = 1; tempmode = (double)arr[i]; i++; } return (mode_return); } В программе указатель на функции (*fun) – это массив указателей на функции, на три функции. В случае, когда возвращаемые значения функций имеют различный тип, то можно определить несколько указателей на функции. Для понимания работы функции по расчету модуса алгоритм вычислений рекомендуется в [11.3] формулировать следующим образом: "Разбить сортированный список значений на ряд меньших списков, каждый из которых содержит одинаковые значения. Пересчитать число элементов в этих списках, и список с наибольшим числом элементов будет соответствовать модусу данных". Результат выполнения программы показан на рис. 11.4.
Задание 3 В функции расчета модуса приведение типов примените только один раз. Массив данных задайте случайным образом (целыми числами). Проанализируйте результат работы программы. Создайте три указателя на функцию – на функцию расчета среднего арифметического, на функцию расчета медианы и на функцию расчета модуса. При этом для массива целых чисел функция расчета модуса должна возвращать целое значение. Объедините три функции – mean(), median(), mode() в одну функцию и определите необходимый тип возвращаемого значения, чтобы в главной функции main() можно была распечатать результаты расчетов статистических характеристик. Напишите программу, в которой с помощью указателя на функции можно было вывести на консоль средние оценки за последние три года обучения в школе.Пример 4. Напишите программу сортировки массива строк с использованием указателей на функции. Программный код решения примера: #include <stdio. h> #include <conio. h> #include <string. h> // Прототипы функций void bsort (char **arr, int size, int (*comp) (const char *s1, const char *s2)); int less (const char *s1, const char *s2); int greater (const char *s1, const char *s2); int main (void) { char *Lines[] = { "asd", "aza", "baza", "qwerty", "hello", "world", "aza" }; int n = sizeof (Lines) / sizeof (Lines[0]); int i; // Вызов функции сортировки по возрастанию в алфавитном порядке puts("\n The sorting in ascending order:"); bsort (Lines, n, less); for (i = 0; i < n; ++i) printf("\t %s\n", Lines[i]); // Вызов функции сортировки по убыванию в алфавитном порядке puts("\n The sorting in descending order:"); bsort (Lines, n, greater); for (i = 0; i < n; ++i) printf("\t %s\n", Lines[i]); printf("\n\n... Press any key: "); _getch(); return 0; } // Определение функции сортировки строк void bsort (char **arr, int size, int (*comp) (const char *s1, const char *s2)) { int i, j; for (i = 0; i < size - 1; ++i) for (j = 0; j < size - 1; ++j) if (comp (arr[j], arr[j + 1]) > 0) { char *s = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = s; } } // Определение функции сравнения строк по возрастанию int less (const char *s1, const char *s2) { return strcmp (s1, s2); } // Определение функции сравнения строк по убыванию int greater (const char *s1, const char *s2) { return - strcmp (s1, s2); } В программе используются указатель на функцию для вызова двух функций – less() и greater() в процессе сортировки для определения порядка расположения элементов (слов). Результат выполнения программы показан на рис. 11.5.
Задание 4 Ввод массива строк осуществите с клавиатуры построчно. Ввод массива строк осуществите с клавиатуры одной строкой, содержащей несколько слов, разделенных пробелами, и заполните символьный массив так, чтобы в каждом элементе было по одному слову из исходной строки. Измените программу так, чтобы при сравнении строк не различались строчные и прописные буквы латинского алфавита. Произведите сортировку по возрастанию длин слов с использованием указателя на функции. Произведите сортировку по уменьшению длин слов с использованием указателей на функции.Пример 5. Напишите программу построения на экране дисплея графика следующей функции:
Предусмотрите возможность записи в текстовый файл графика данной функции. Для решения примера используем средства вывода на печать форматированных данных без применения специальных графических библиотек. Программный код решения примера: / Заголовочные файлы #include <stdio. h> #include <conio. h> #include <stdlib. h> #include <math. h> // Размеры диаграммы по ширине и высоте экрана #define SCREENW 79 #define SCREENH 25 // Функция построения графика заданной функции void plot (FILE *fout, double a, double b, double (*f) (double)) { // Формальные параметры функции plot // FILE *fout – указатель на поток вывода // double a – левая граница оси абсцисс // double b – правая граница оси абсцисс // double (*f) (double) – указатель на функцию char screen[SCREENW][SCREENH]; double x, y[SCREENW]; double ymin = 0, ymax = 0; double hx, hy; int i, j; int xz, yz; // hx – шаг по оси абсцисс hx = (b - a) / (SCREENW - 1); for (i = 0, x = a; i < SCREENW; ++i, x += hx) { // вычисляем значение функции y[i] = f (x); // запоминаем минимальное и максимальное значения if (y[i] < ymin) ymin = y[i]; if (y[i] > ymax) ymax = y[i]; } hy = (ymax - ymin) / (SCREENH - 1); yz = (int)floor (ymax / hy + 0.5); xz = (int)floor (-a / hx + 0.5); // рисование осей координат for (j = 0; j < SCREENH; ++j) { for (i = 0; i < SCREENW; ++i) { if (j == yz && i == xz) screen[i][j] = '+'; // '.', '?', '+' else if (j == yz) screen[i][j] = '-'; else if (i == xz) screen[i][j] = '|'; else screen[i][j] = ' '; } } // рисование графика функции for (i = 0; i < SCREENW; ++i) { j = (int)floor ((ymax - y[i]) / hy + 0.5); screen[i][j] = '.'; // символ начертания графика } // вывод результата в файл или в стандартный поток stdout for (j = 0; j < SCREENH; ++j) { for (i = 0; i < SCREENW; ++i) fputc (screen[i][j], fout); fprintf (fout, "\n"); } } // Заданная функция double f (double x) { return sin (3.0*x) * exp (-x / 3.0); //return x * x - 3; } int main (void) { // Вывод графика в стандартный поток (консоль) plot (stdout, 0.0, 10.0, f); printf("\n\n … Press any key: "); _getch(); return 0; } В программе используется указатель на файл, который может быть стандартным потоком, т. е. экран дисплея. В главной функции main() происходит обращение к функции рисования графика plot(), в которую вводят фактические параметры, в частности файл – это stdout, т. е. стандартный поток, 0.0 – это левая граница оси абсцисс, 10.0 – правая граница оси абсцисс, f – имя функции с описанием зависимости y = f(x). Пример выполнения программы показан на рис. 11.6.
Задание 5 Внесите в программу изменения для вывода графика в текстовый файл с именем compX. txt, где Х – номер компьютера, на котором выполняется лабораторная работа. Проанализируйте программу с целью возможного улучшения вида графика заданной функции. В программу добавьте описание кубической и параболической функций. Напишите необходимые строчки программного кода для запроса о построении графика соответствующей функции.Контрольные вопросыКаким образом можно вернуть из функции несколько значений? Каким образом определяется тип функции? Как выглядит описание функции, которая возвращает указатель на заданный тип, например, char? Можно ли использовать многоуровневую адресацию для функции, которая возвращает указатель на заданный тип? Если можно, то как происходит определение такой функции? В каком месте программы можно определить указатель на функцию? Имеет ли указатель на функцию прототип и определение? Как осуществляется вызов функции с помощью указателя? Как взаимосвязаны между собой объявление функции, ее определение и вызов функции? | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Программирование на языке C в Microsoft Visual Studio 2010 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
12. Лекция: Файловый ввод/вывод в языке С: В лекции предполагается изучить базовые функции файловой системы языка программирования С. Научиться создавать, читать, записывать и модифицировать файлы. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Теоретическая часть Файл – это именованный объект, хранящий данные (программа или любая другая информация) на каком-либо носителе (дискета, винчестер, CD) [12.1]. В языке С файлом может быть все что угодно, начиная с дискового файла и заканчивая терминалом или принтером [12.2]. Поток связывают с определенным файлом, выполняя операцию открытия. Как только файл открыт, можно проводить обмен информацией между ним и программой. Не у всех файлов одинаковые возможности. Например, к дисковому файлу прямой доступ возможен, в то время как к некоторым принтерам – нет. В языке С все потоки одинаковы, а файлы – нет [12.2]. Если файл может поддерживать запросы на местоположение (указатель текущей позиции), то при открытии такого файла указатель текущей позиции в файле устанавливается в начало. При чтении из файла (или записи в него) каждого символа указатель текущей позиции увеличивается, обеспечивая тем самым продвижение по файлу [12.2]. Файл отсоединяется от определенного потока (т. е. разрывается связь между файлом и потоком) с помощью операции закрытия. При закрытии файла, открытого с целью вывода, содержимое (если оно есть) связанного с ним потока записывается на внешнее устройство. Этот процесс, который обычно называют дозаписью потока, гарантирует, что никакая информация случайно не останется в буфере диска. Если программа завершает работу нормально, т. е. либо функция main() возвращает управление операционной системе, либо вызывается функция exit(), то все файлы закрываются автоматически. В случае аварийного завершения программы, например, в случае краха или завершения путем вызова функции abort(), файлы не закрываются [12.2]. Файловая системы языка С предназначена для работы с самыми разнообразными устройствами, в том числе терминалами, дисками и накопителями на магнитной ленте. Даже если какое-то устройство сильно отличается от других, буферизованная файловая система все равно представит его в виде логического устройства, которое называется потоком. Потоки бывают двух видов: текстовые и двоичные [12.2]. Текстовый поток – это последовательность символов. В стандарте С считается, что текстовый поток организован в виде строк, каждая из которых заканчивается символом новой строки. Однако в конце последней строки этот символ не является обязательным. В текстовом потоке по требованию базовой среды могут происходить определенные преобразования символов. Например, символ новой строки может быть заменен парой символов – возврата каретки (например, \r) и перевода строки (например, \n), т. е. \r\n. Двоичные потоки – это последовательность байтов, которая взаимно однозначно соответствует байтам на внешнем устройстве, причем никакого преобразования символов не происходит [12.2]. Кроме того, количество тех байтов, которые пишутся (читаются), и тех, которые хранятся на внешнем устройстве, одинаково. Однако в конце двоичного потока может добавляться определяемое приложением количество нулевых байтов. Такие нулевые байты, например, могут использоваться для заполнения свободного места в блоке памяти незначащей информацией, чтобы она в точности заполнила сектор на диске. Файловая система языка С состоит из нескольких взаимосвязанных функций [12.2]. Самые распространенные из них показаны в табл. 12.1.
Для приведенных функций требуется подключить заголовок <stdio. h>. Запись или чтение из файла осуществляются с помощью указателя файла. Указатель файла – это указатель на структуру типа FILE. Для объявления переменной–указателя файла, например, *fp, используется следующий оператор: FILE *fp; Ключевое слово FILE определяет собой своеобразный тип данных, а указатель *fp указывает на этот тип. Указатель файла указывает на структуру, содержащую различные сведения о файле, его имя, статус и указатель текущей позиции в начало файла [12.2]. Открытие файла осуществляется с помощью функции fopen(), которая открывает поток и связывает с этим потоком определенный файл. Прототип функции fopen() такой: FILE *fopen(const char *file_name, const char *mode); В прототипе функции fopen() формальные переменные имеют следующий смысл: file_name – это имя файла с заданным расширением и возможным путем расположения, mode – режим работы файла: чтение, запись и т. д. [12.1]. В табл. 12.2, взятой из [12.2], приводятся допустимые значения режима для функции fopen().
Например, для записи в файл с именем (и расширением) data. txt на диск D следует использовать такие объявление и операции: FILE *fp; fp = fopen("D: \\data. txt", "w"); fprintf(fp, "\n\t hello, world\n"); fclose(fp); В приведенном фрагменте С-кода функция fclose() закрывает поток, который был открыт с помощью вызова функции fopen(). Функция fprintf() осуществляет форматную запись (в данном случае строку hello, world) в файл. Все манипуляции с файлом происходят между функциями fopen() и fclose(). Режим функции fopen() задается строкой "w", которая обеспечивает создание текстового файла для записи. Это означает, что файл data. txt создается на диске D и в него записывается строка hello, world с отступом от верхнего края и с отступом (табуляцией) от левого края. Прототип функции fclose() следующий: int fclose(FILE *fp); В приведенной записи *fp – указатель файла, возвращенный в результате вызова функции fopen()[12.2]. Возвращение нуля означает успешную операцию закрытия. В случае же ошибки возвращается EOF. Обычно отказ при выполнении функции fclose() происходит только тогда, когда диск был преждевременно удален из дисковода или на диске не осталось свободного места. Правомочность открытия файла с помощью функции fopen() обычно подтверждается после проверки какой-либо ошибки, например, когда на диске нет места для записи или неправильного имени диска, причем эти ошибки будут обнаружены до того, как программа попытается в этот файл что-либо записать. Поэтому приведенный фрагмент С-кода будет правильным, если производится проверка возможности открытия файла: FILE *fp; if ((fp = fopen("D:\\data. txt", "w")) == NULL) { //exit(1); printf("\n\t Error! Can not open file\n "); printf("\n Press any key: "); _getch(); return 0; } fprintf(fp, "\n\t hello, world\n"); fclose(fp); При выполнении условия проверки можно выходить при нажатии любой клавиши с заданным сообщением или немедленный выход сделать с помощью функции exit(), которая в данном фрагменте С-кода закомментирована. Функции для работы с текстовыми файлами удобно использовать при создании текстовых файлов, ведении файлов-протоколов и т. п. Но при создании баз данных целесообразно использовать функции для работы с бинарными файлами: fwrite() и fread(). Эти функции без каких-либо изменений копируют выделенный блок данных из оперативной памяти в файл и, соответственно, из файла – в память [12.1]. При записи или чтении суффикс "t" открывает файл в текстовом режиме. В этом режиме символ CTRL+Z (символ с кодом 26) обрабатывается как символ конца файла. Кроме того, комбинации символов перевода строки и возврата каретки преобразуются в единственный символ перевода строки ('\n') при вводе, и символы перевода строки преобразуются в комбинации символов перевода строки и возврата каретки при выводе. Суффикс "b" открывает файл в бинарном режиме, преобразования символов перевода строки и возврата каретки не производятся. FILE *fp; if ((fp = fopen("D:\\data. txt", "w")) == NULL) { //exit(1); printf("\n\t Error! Can not open file\n "); printf("\n Press any key: "); _getch(); return -1; } fprintf(fp, "\n\t hello, world\n"); fclose(fp); Практическая часть Пример 1. Напишите программу заполнения матрицы размера n Программный код решения примера: #define _CRT_SECURE_NO_WARNINGS #include <stdio. h> #include <conio. h> #include <stdlib. h> int main(void) { int i, j, x, xi, n, m, *matr; FILE *fid; char str[] = "D:\\data. txt"; // месторасположение файла if ((fid = fopen(str, "w")) == NULL){ printf("\n\t The file could not be opened.\n "); printf("\n Press any key: "); _getch(); return 0; } printf("\n\t Enter the number of lines: "); scanf_s("%d", &n); printf("\t Enter the number of columns: "); scanf_s("%d", &m); printf("\t Enter the odd number: "); scanf_s("%d", &x); xi = x; matr = (int *)calloc(n*m, sizeof(int)); // Заполнение матрицы целыми числами for (i = 0; i < n; ++i) for (j = 0; j < m; ++j) {matr[i*m + j] = x; x += 2; } printf("\n\t Matrix (%d x %d), initial number: %d\n", n, m, xi); fprintf(fid, "\r\n\t Matrix (%d x %d), initial number: %d\r\n", n, m, xi); for (i = 0; i < n; ++i){ printf("\n "); fprintf(fid, "\r\n "); for (j = 0; j < m; ++j){ printf("%5d", matr[i*m + j]); fprintf(fid, "%5d", matr[i*m + j]); } } fclose(fid); printf("\n\n Result of record look in file %s\n", str); printf("\n Press any key: "); _getch(); return 0; } В программу включена препроцессорная директива #define... для устранения предупреждения о ненадежной работе функции fopen() в Visual Studio 2008. Возможный результат выполнения программы показан на рис. 12.1.
Текстовый файл с заполненной матрицей показан на рис. 12.2.
Примечание. В текстовом файле следует использовать моноширинный (равноширинный) шрифт, например, Courier New. Задание 1 Вместо функции calloc() примените функцию malloc(). Найдите сумму элементов столбцов матрицы. Результат запишите под матрицей. Произведите заполнение матрицы нечетными числами по столбцам. Запишите сформированную матрицу в файл с расширениями. doc, .xls, .rtf, .csv. Просмотрите полученные файлы. Сформируйте матрицу из четных чисел размером (3*Х)Пример 2. Напишите программу посимвольной записи в текстовый файл некоторой строки, набранной на консоли, и посимвольного чтения из текстового файла с перезаписью в другой текстовый файл. В качестве строки возьмем "hello, world". Для посимвольной записи в файл используем функции putc() и fputc(), которые реализованы для сохранения совместимости со старыми версиями языка С. Для прекращения чтения символов с клавиатуры используем точку, т. е. '.'. Программный код решения примера: #define _CRT_SECURE_NO_WARNINGS #include <stdio. h> #include <conio. h> int main(void){ char ch; FILE *f_in, *f_out; char str[] = "D:\\data. txt"; // Файл записи char str2[] = "D:\\data2.txt";// Файл перезаписи if ((f_in = fopen(str, "w")) == NULL){ printf("\n\t The file could not be opened.\n "); printf("\n Press any key: "); _getch(); return 0;} printf("\n Enter the characters by pressing Enter and exit point:\n\n> "); while ((ch = getchar()) != '.') { printf(" "); fputc(ch, f_in);} fclose(f_in); if ((f_in = fopen(str, "r")) == NULL){ printf("\n\t The file could not be opened.\n "); printf("\n Press any key: "); _getch(); return 0; } if ((f_out = fopen(str2, "w")) == NULL){ printf("\n\t The file could not be opened.\n "); printf("\n Press any key: "); _getch(); return 0;} while((ch = getc(f_in)) != EOF) putc(ch, f_out); fclose(f_in); fclose(f_out); printf("\n\n Result of record look in file %s\n", str); printf(" Result of rewriting look in file %s\n", str2); printf("\n Press any key: "); _getch(); return 0; } В программе сначала открывается файл data. txt для записи в него символов, затем он закрывается. После этот же файл открывается для считывания записанных символов с целью записи их в другой файл, под именем data2.txt. Всякий раз производится проверка возможности открытия файлов. Запись символов в текстовый файл выполняется с помощью функций putc() и fputc(). Считывание символов из файла осуществляется с помощью функции getc(). В среде Visual Studio стандартная функция fopen() языка С вызывает предупреждение при компиляции программы. Для устранения предупреждений при компиляции в программу включена препроцессорная директива #define_CRT_SECURE_NO_WARNINGS. Результат выполнения программы показан на рис. 12.3.
Задание 2 Проверьте функцию fgetc() для чтения символов из файла. Посимвольную запись в текстовый файл строки "hello, world" осуществите с предварительной инициализацией соответствующего символьного массива. Программу выполните для записи в текстовые файлы с фамилией пользователя. Подготовьте текстовый файл со своей фамилией, именем, номера группы и специальности. Выполните чтение символов из этого файла с выводом на консоль и перезаписи в другой файл. Имя файла примите за compX. txt, где Х – номер компьютера, за которым выполняется лабораторная работа. Запишите в текстовый файл прописные буквы латинского алфавита и их коды.Пример 3. Напишите программу чтения из текстового файла массива строк, вывода этих строк на консоль и записи их в другой файл. Для решения примера используем функции fgets() – для чтения строк из текстового файла, fputs() – для записи строк в текстовый файл. Содержание текстового файла для считывания – это обложка книги [12.3]: ============================================= THE _____________________________________________ C _____________________________________________ PROGRAMMING LANGUAGE ============================================= Second Edition BRIAN W. KERIGHAN DENNIS M. RITCHIE AT & T Bell Laboratories Murray Hill, New Jersey Prentice Hall PTR, Upper Saddle River, New Jersey 07458 _________________________________________________________ Файлу, из которого будет считываться информация, присвоим имя data3.txt. Файлу, куда будет записываться информация, присвоим имя data33.txt. Программный код решения примера: #include <stdio. h> #include <conio. h> int main(void){ char str[255+1]; char data3[] = "D:\\data3.txt"; char data33[] = "D:\\data33.txt"; FILE *fid, *fid2; errno_t err; if ((fopen_s(&fid, "D:\\data3.txt", "r")) || (err = fopen_s(&fid2, "D:\\data33.txt", "w")) != 0) { printf("\n\t The file could not be opened.\n "); printf("\n Press any key: "); _getch(); return 0; } while (fgets(str, 255, fid) != NULL) // Чтение из data3.txt {fputs(str, stdout); // Вывод на консоль fputs(str, fid2); // Запись в файл data33.txt } fclose(fid); fclose(fid2); printf(" Read the information was produced from a file %s\n", data3); printf(" Recorded information has been made to the file %s\n", data33); printf("\n Press any key: "); _getch(); return 0; } В программе с помощью логического условия ИЛИ ( || ) производится проверка корректности открытия файла data3.txt для чтения и проверка открытия файла data33.txt для записи. Вместо функции fopen() используется функция fopen_s(), которая применяется в MS Visual Studio. Функция fgets() считывает строки из файла, на который имеется файловый указатель *fid, записывает их в символьный массив str[256]. Одно поле функции fgets() используется для определения количества считываемых символов с учетом символа завершения строки. Первая функция fputs() используется для вывода информации на консоль с помощью определения стандартного выходного потока stdout, который указывает на "обычное" средство вывода – дисплей. Вторая функция fputs() выводит символьный массив str[] в файл data33.txt с помощью файлового указателя *fid2. Вывод на дисплей и запись в файл будет осуществляться до тех пор, пока при чтении из файла data3.txt не обнаружится признак конца файла, т. е. NULL. Проверку можно выполнять также по числу считанных символов, а именно n–1, т. е. в данном случае 255 символов из 256. Результат выполнения программы с выводом текстовой информации на консоль показан на рис. 12.4.
Результат записи информации в текстовый файл показан на рис. 12.5.
Задание 3 Подсчитайте число итераций, выполняемых функцией while(). Вывод на консоль выполните с помощью функции printf(), а запись в файл – с помощью функции fprintf(). Напишите программу ввода строк с клавиатуры и записи введенной информации в текстовый файл с именем compX. txt, где Х – номер компьютера, за которым выполняется лабораторная работа. В качестве вводимой информации используйте: свою фамилию, имя, число, месяц и год рождения, специальность, на которой вы учитесь, номер группы. Напишите программу записи в текстовый файл compX. txt матрицы вещественных случайных чисел из интервала [–X; X]. Произведите считывание матрицы из файла с выводом ее значений на консоль и записью еще в другой текстовый файл. Размер матрицы примите nПримечание. Для п.5 задания 3 примените массив указателей для считывания строк разной длины. Пример 4. Напишите программу форматированной записи в текстовый файл трех строк различной длины и одномерного целочисленного массива. Произведите чтение из текстового файла с выводом его содержания на консоль и преобразования одномерного массива в двухмерный. Для решения примера используем функции fprintf(), fgets(), atoi(), fscanf(). Программный код решения примера: #define _CRT_SECURE_NO_WARNINGS #include <stdio. h> #include <conio. h> #include <stdlib. h> #define n 4 // Число строк матрицы #define m 3 // Число столбцов матрицы #define N 123 // Число считываемых строк из текстового файла int main(void) { int i, j = 0; int A[n*m] = {1,2,3,4,5,6,7,8,9,10,11,12}; int B[n*m]; FILE *fid; char *str[] = {"aza","baza","qwerty"}; char str2[N][80]; // Буферный массив // Обнуление массива B[n*m] for (i = 0; i < n*m; ++i) B[i] = 0; if ((fid = fopen("D:\\data4.txt", "w")) == NULL) {printf("\n\t The file could not be opened.\n "); printf("\n Press any key: "); _getch(); return 0; } // Запись в файл data4.txt fprintf(fid, "\n\t The lines are:\n"); for (i = 0; i < m; ++i) fprintf(fid,"\t %s\n", str[i]); for (i = 0; i < n*m; ++i) fprintf(fid, " %3d", A[i]); fclose(fid); printf("\n\t From file \"data4.txt\":\n"); if ((fid = fopen("D:\\data4.txt", "r")) == NULL) {printf("\n\t The file could not be opened.\n "); printf("\n Press any key: "); _getch(); return 0; } // Чтение из файла data4.txt for (i = 0; (fgets(str2[i], 80, fid) != NULL) && (i < N); ++i) printf(" %s",str2[i]); fclose(fid); if ((fid = fopen("D:\\data4.txt", "r")) == NULL) {printf("\n\t Error! You can not open the file \n "); printf("\n Press any key: "); _getch(); return 0; } // Повторное чтение из файла data4.txt for (i = 0; fscanf (fid, "%s", str2[i]) != EOF; ++i) if (atoi(str2[i])) { B[j] = atoi(str2[i]); ++j; } fclose(fid); printf("\n\n\t The reconfigured array:\n"); for (i = 0; i < n; ++i) { printf("\n\t"); for (j = 0; j < m; ++j) printf("%5d", B[i*m+j]); } printf("\n\n Press any key: "); _getch(); return 0; } Для форматированной записи в текстовый файл и чтения из файла применены массивы указателей *str[], str2[123][80]. Чтение из файла одномерного массива целых чисел выполняется с помощью функции atoi(), значения целых чисел заносятся сначала в одномерный массив B[n*m]. После закрытия файла data4.txt одномерный массив B[n*m] выводится на консоль в виде двухмерной матрицы размера 4 Возможный результат выполнения программы показан на рис. 12.6.
Задание 4 Для вывода информации из текстового файла поочередно используйте функции fgets() и fscanf(). Инициализацию массива А выполните как инициализацию двухмерной матрицы с возможными сочетаниями числа строк и столбцов. В качестве строк разной длины используйте свою фамилию, имя, специальность и номер учебной группы. Массивы чисел определите как вещественные. Размерность массива задайте с клавиатуры и заполните его натуральными числами по строкам. Предусмотрите проверку возможности преобразования одномерного массива в двухмерный массив. Если возможно преобразование одномерного массив, то выполните вывод двухмерного массива в текстовый файл с именем compX. txt, где Х – номер компьютера, за которым выполняется лабораторная работа.Пример 5. Напишите программу добавления слов в текстовый файл с контролем на консоли [9]. В текстовый файл запишем название книги и авторов [3]. После будем добавлять слова, символы и т. д. Для программного решения примера используем функции файлового ввода/вывода fprintf(), fgets() и rewind(). Кроме того, подключим библиотеку locale. h и объявим прототип функции, что позволит использовать шрифты русского алфавита:> #include <locale. h> setlocale( LC_ALL, "Russian"); или setlocale( LC_ALL, ".1251");//кодовая страница Windows–1251 Программный код решения примера: #define _CRT_SECURE_NO_WARNINGS #include <stdio. h> #include <conio. h> #include <locale. h> #define MAX 40 int main(void) { FILE *fid; char words[MAX+1]; char str_name[] = "D:\\data5.txt"; // Прототип функции поддержки русских шрифтов setlocale( LC_ALL, "Russian"); // setlocale( LC_ALL, ".1251"); if ((fid = fopen(str_name, "a+")) == NULL) {fprintf(stdout, "\n\t Файл не может быть открыт \"%s\".\n ", str_name); printf("\n Нажмите любую клавишу: "); _getch(); return -1; } printf("\n\t Введите слова для включения их в файл \"%s\"\n\t\ и нажмите клавишу Enter в начале строки для завершения ввода\n\t: ", str_name); // Запись в файл data5.txt while (gets_s(words, MAX) != NULL && words[0] != '\0') {printf("\t: "); fprintf(fid," %s\n", words); } puts("\t Содержимое файла:"); // Устанавливает указатель текущей позиции в начало файла rewind(fid); // Сканирование файла while (fgets(words, MAX, fid) != '\0') printf("\t%s", words); if (fclose(fid) != 0) fprintf(stderr, "\n\t Ошибка при закрытии файла \"%s\"\n", str_name); printf("\n\n Нажмите любую клавишу (Press any key): "); _getch(); return 0; } В программе введены две проверки: на открытие файла if (... == NULL) и на закрытие файла if (... != 0). Эти проверки позволяют исключить аварийный выход из программы. Использование в функции форматного вывода fprintf() ключевого слова stdout позволяет выводить сообщения на консоль – дисплей пользователя. Вместо стандартной функции gets() использована функция gets_s(), которую поддерживает MS Visual Studio. При работе в MS Visual Studio с функцией gets() появляются предупреждения (которыми в общем случае можно пренебречь). Предупреждения возникают и при работе с функцией fopen(). Вместо нее можно использовать fopen_s() в следующем формате записи: fopen_s(&fid, "D:\\data5.txt","a+"); Тогда проверку на открытие файла следует изменить, например: if (fopen_s(&fid, "D:\\data5.txt","a+")) {fprintf(stdout, "\n\t Ошибка! Не удается открыть файл \"data5.txt\".\n "); printf("\n Нажмите любую клавишу: "); _getch(); return -1; } Если файл data5.txt сохранить, то при последующих выполнениях программы в этот файл будут дописывать данные. Это обеспечивает режим "a+" функции fopen(). Возможный результат выполнения программы показан на рис. 12.7.
Задание 5 В программу при записи в файл и чтения из файла введите свою фамилию, имя, отчество, курс обучения, специальность. В программе используйте функцию fopen_s() вместо функции fopen(). Отметьте результат компиляции. В программу вместо функции fgets() включите функцию fscanf(). Отметьте результат записи и чтения нескольких строк, состоящих из нескольких слов.Примечание. Для данной программы формат записи функции fscanf(): fscanf(fid, "%s", words); Пример 6. Напишите программу записи в файл нескольких строк и отображения содержимого файла в обратном порядке, как на консоли, так и в другом текстовом файле. Для решения примера используем функции fseek() и ftell(). Программный код решения примера: #include <stdio. h> #include <conio. h> #define MAX 79 #define file "D:\\data6.txt" // запись в прямом порядке #define file2 "D:\\data66.txt" // запись в обратном порядке int main(void) { char ch, str[MAX+1]; long n, m; FILE *fid, *fid2; if ( fopen_s(&fid, file, "w") ) { fprintf(stdout, "\n\t The file could not be opened.\n "); printf("\nPress any key: "); _getch(); return 0; } printf("\n\t Enter a few lines and press Enter to complete before the new line\n\t: "); // Запись в файл data6.txt while (gets_s(str, MAX) != NULL && str[0] != '\0') { printf("\t: "); fprintf(fid," %s\n", str); } fclose(fid); if ( fopen_s(&fid, file, "r") ) { fprintf(stdout, "\n\t File could not be opened.\n"); printf("\n Press any key: "); _getch(); return 0; } if ( fopen_s(&fid2, file2, "w") ) { fprintf(stdout, "\n\t File could not be opened.\n"); printf("\n Press any key: "); _getch(); return 0; } //Переход в конец файла fseek(fid, 0L, SEEK_END); m = ftell(fid); for (n = 1L; n <= m; n++) { fseek(fid, - n, SEEK_END); ch = getc(fid); if (ch!= '\n') { printf(" "); putchar(ch); fprintf(fid2, " "); putc(ch, fid2); } } // End for putchar('\n'); fclose(fid); fprintf(fid2, "%c", '\n'); fclose(fid2); printf("\n Result see the files, \"%s\" and \"%s\"\n", file, file2); printf("\n Press any key: "); _getch(); return 0; } Работу функций fseek() и ftell() опишем в соответствии с [9]. Функция fseek() имеет следующую форматную запись: fseek(fid, 0L, SEEK_END); Она определяет позицию со смещением в 0 байт от конца файла (именованная константа SEEK_END). Суффикс L означает тип long int. Строка с функцией ftell() определяет количество байтов от начала до конца указанного файла. Это количество байтов записывается в переменную m: m = ftell(fid); Рассмотрим следующий программный цикл: for (n = 1L; n <= m; n++) { fseek(fid, - n, SEEK_END); ch = getc(fid); if (ch!= '\n') { printf(" "); putchar(ch); fprintf(fid2, " "); putc(ch, fid2); } } // End for Первое выполнение цикла выводит программу на первый символ перед концом файла. Затем программа печатает этот символ на консоль и записывает в новый файл с именем data66.txt. Следующая итерация цикла выводит программу на предпоследний символ файла, который она печатает и записывает в новый файл. Этот процесс продолжается до тех пор, пока программа не выйдет на первый символ файла и не распечатает его (и запишет в файл). Возможные результаты выполнения программы показаны на рис. 12.8-рис. 12.9–рис. 12.10.
Задание 6 Вывод информации в дополнительный текстовый файл сделайте построчно, и каждую строку запишите в обратном порядке (для рассмотренного примера в три строки). Вместо функции putchar() примените printf(). Вместо функции putc() примените функцию с тем же действием. Подготовьте текстовый файл со своей фамилией, инициалами, номером группы, специальности. Выполните чтение из этого файла в обратном порядке и вывести на консоль и запишите в дополнительный текстовый файл с именем compX. txt, где Х – номер компьютера, за которым выполняется лабораторная работа.Пример 7. Создайте файл последовательного доступа и записать в него информацию, состоящую из целых чисел, строки символов и вещественных чисел. После произведенной записи выведите содержимое файла на консоль и перезапишите в другой файл. Предположим, что имеются номера ячеек, их имена (например, по фамилии владельца) и определенное количество денег в условных единицах (у. е.). Для форматированного считывания данных из файла применим библиотечную функцию fscanf(). Программный код решения примера: #define _CRT_SECURE_NO_WARNINGS #include <stdio. h> #include <conio. h> #define MAX 39 // Предполагаемое число символов в имени #define file "D:\\data7.dat" #define file2 "D:\\data77.dat" int main(void) { int number, i = 1, j = 1; // номер ячейки char name[MAX+1]; // имя ячейки (владельца) long double sum; // сумма денег в у. е. FILE *fid, *fid2; if ( fopen_s(&fid, file, "w") ) {fprintf(stdout, "\n\t File could not be opened\n"); printf("\n Press any key: "); _getch(); return 0; } printf("\n Enter through blanks number of a cell,\n a name of the owner and the sum of money.\n \ Type Ctrl+Z to exit at the beginning of a new line: \n\n"); printf(" %3d) ", i); scanf("%d%s%lf", &number, name, &sum); // Запись в файл data7.dat while ( !feof(stdin)) { // stdin - поток с клавиатуры fprintf(fid, " %3d\t %-15s %7.2f\r\n", number, name, sum); printf(" %3d) ", ++i); scanf("%d%s%lf", &number, name, &sum); } fclose(fid); if ( fopen_s(&fid, file, "r") ) { fprintf(stdout, "\n\t File could not be opened\n"); printf("\n Press any key: "); _getch(); return 0; } if ( fopen_s(&fid2, file2, "w") ) { fprintf(stdout, "\n\t File could not be opened\n"); printf("\n Press any key: "); _getch(); return 0; } // Вывод на консоль printf("\n %s\t %s\t\t %5s", "The number of cell", "Name", "Sum"); // Чтение из файла data7.dat fscanf(fid, "%d%s%Lf", &number, name, &sum); fprintf(fid2, "\r\n %s\t\t %s\t\t %5s\r\n", "The number of cell", "Name", "Sum"); i = 1; while ( !feof(fid)) { // пока не конец файла // Вывод на консоль printf("\n %3d\t\t\t %-17s %1.2f", number, name, sum); // Запись в файл data77.dat fprintf(fid2, " %3d) %3d\t\t\t %-17s %1.2f\r\n", i++, number, name, sum); // Чтение из файла data7.dat fscanf(fid, "%d%s%Lf", &number, name, &sum); } fclose(fid); fclose(fid2); printf("\n\n\n Result see the files, \"%s\" and \"%s\"\n", file, file2); printf("\n Press any key: "); _getch(); return 0; } В программе использована функция feof(), которая проверяет, достигнут ли конец файла, связанного с потоком (указателем на файл) fid. На рис. 12.11 показан возможный результат выполнения программы.
Следует обратить внимание на прекращение ввода данных с клавиатуры с помощью комбинации клавиш Ctrl+Z. Задание 7 Вместо оператора цикла while примените оператор цикла for. Отсортируйте записи владельцев ячеек по убыванию величины суммарной денежной суммы. Подготовьте форматированный текстовый файл с именем compX, где Х – номер компьютера, за которым выполняется лабораторная работа. Затем информацию из файла выведите на консоль.Пример 8. Напишите программу пакетной записи в файл произвольного доступа массива данных и вывода этого пакета на консоль. Для решения примера применим функции fwrite() и fread() для бинарной записи и считывания информации. Программный код решения примера: #include <stdio. h> #include <conio. h> #define MAX 20 #define n 5 #define m 4 #define file "D:\\data8.txt" int main(void) { //Матрица 5х4 int mass[MAX][MAX] = { {1,2,3,4}, {5,6,7,8}, {9,10,11,12}, {13,14,15,16}, {17,18,19,20} }; int mass2[MAX][MAX]; // Вспомогательная матрица int i, j; // Массив из 5 указателей char *str[] = { "Brian W. Kernighan", "Dennis M. Ritchie", "Stephen Prata", "Herbert Shildt", "The C Programming Language" }; char *str2[n]; // Вспомогательный массив указателей FILE *fid; if ( fopen_s(&fid, file, "wb") ) { fprintf(stdout, "\n\t File could not be opened\n"); printf("\n Error! Press any key: "); _getch(); return 0; } fwrite(str, sizeof(char), sizeof(str)/sizeof(char), fid); fwrite(mass, sizeof(int), sizeof(mass)/sizeof(int), fid); fclose (fid); if ( fopen_s(&fid, file, "rb") ) { fprintf(stdout, "\n\t File could not be opened\n"); printf("\n Press any key: "); _getch(); return 0; } fread(str2, sizeof(char), sizeof(str)/sizeof(char), fid); fread(mass2, sizeof(int), sizeof(mass2)/sizeof(int), fid); // Чтение элементов из файла printf("\n\t From a file \"%s\": \n\n", file); for (i = 0; i < n; ++i) printf("\t %-15s\n", str2[i]); printf("\n\t Matrix from a file \"%s\":\n", file); for (i = 0; i < n; ++i) { printf("\n\t"); for (j = 0; j < m; ++j) printf(" %3d", mass2[i][j]); } fclose(fid); printf("\n\n\n Press any key: "); _getch(); return 0; } Функция fwrite() пересылает в файл заданное количество байт начиная с указанного адреса памяти. Данные записываются с того места в файле, которое обозначено указателем позиции файла. Функция fread() пересылает заданное количество байт на места в файле, определенного указателем позиции файла, в массив в памяти, начинающийся с указанного адреса. В программе пакетная запись информации – набора строк и матрицы целых чисел производится через двоичный поток с помощью функций fwrite(). Чтение информации из двоичного файла осуществляется функцией fread(). Форматы записи обеих функций одинаковый, так как в них требуется определить количество объектов с заданным размером байт, которые определяются функцией sizeof(). Результат выполнения программы показан на рис. 12.12 и на рис. 12.13.
Как видно из рисунков, информация на консоли соответствует исходной информации, а в двоичном файле информация не подлежит непосредственному восприятию. Примечание. Вид бинарной информации в текстовом файле зависит от установленных шрифтов. Задание 8 Запишите двоичную информацию в файлы с расширением. dat, .doc, .bin. Проанализируйте файлы после их открытия. Вместо массива указателей примените двухмерный символьный массив необходимой размерности. Вместо двухмерного массива целых чисел примените целочисленный указатель. Перезапишите информацию из двоичного файла в текстовый файл с именем compX. txt, где Х – номер компьютера, за которым выполняется лабораторная работа.Контрольные вопросы Что может быть файлом в языке С? Какие обязательные операции выполняются при нормальной работе с файлами? Какие библиотечные функции при этом используются? Как определяется текстовой поток в стандарте языка С? Как определяется двоичный поток в стандарте языка С? Что определяет собой указатель файла? С помощью каких функций языка С осуществляется форматная запись в файл и форматное чтение данных из файла? Какая переменная стандартной библиотеки используется для определения стандартного потока вывода на дисплей? Какая переменная стандартной библиотеки используется для определения стандартного потока чтения с дисплея? Как в языке С кодируется признак конца файла? Как в языке С кодируется признак конца строки? Что такое файл произвольного доступа? Как в языке С осуществляется пакетная запись данных в файл? Как осуществляется запись бинарной информации в текстовый файл? Как осуществляется чтение бинарной информации из текстового файла? | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Программирование на языке C в Microsoft Visual Studio 2010 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
13. Лекция: Структуры – производные типы данных языка С: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Теоретическая часть Структура – это совокупность нескольких переменных, часто различных типов [13.1]. В структурах совокупность переменных объединяют под одним именем. Переменные, из которых состоит структура, называются ее членами. Члены структуры еще называются элементами или полями [13.2]. С помощью структур удобно размещать в смежных полях связанные между собой элементы информации. В структуре могут быть собраны различные объекты – переменные, массивы, указатели, другие структуры и т. д., которые для удобства работы с ними сгруппированы под одним именем. Если в массиве собраны переменные одного типа (например, float), то в структуре могут быть переменные различных типов. Объявление структуры создает шаблон, который можно использовать для создания ее объектов (то есть экземпляров этой структуры) [13.2]. При объявлении структуры определяется агрегатный тип данных, но не переменная Определение структуры состоит из двух шагов: объявление шаблона структуры (задание нового типа данных, определенного пользователем); определение переменных типа объявленного шаблона [13.3].Имена шаблонов должны быть уникальными в пределах их области определения, для того чтобы компилятор мог различать типы шаблонов. Задание шаблона осуществляется с помощью ключевого слова struct, за которым следует имя шаблона структуры и списка элементов, заключенных в фигурные скобки [13.3]. Имена элементов в одном шаблоне также должны быть уникальными. Задание только шаблона не влечет резервирования памяти компилятором. Шаблон предоставляет компилятору необходимую информацию об элементах структурной переменной для резервирования места в оперативной памяти и организации доступа к ней при определении структурной переменной и использовании ее отдельных элементов [13.3]. Рассмотрим шаблон структуры для определения имени и фамилии работника (служащего – employee), его возраста, почасовой оплаты: struct employee { char Name [20+1]; // Имя char Surname [20+1]; // Фамилия int age; // возраст double hourlysalary; // почасовой оклад }; Как видно, шаблон структуры struct employee состоит из символьных массивов типа char, целого числа типа int и числа с плавающей точкой и двойной точности типа double. Описания элементов производится в фигурных скобках, после закрывающей скобки обязательно должна быть точка с запятой. Определение структуры начинается с ключевого слова struct. Идентификатор employee является именем-этикеткой (tag name), дескриптором. Имя-этикетка employee именует структуру и используется совместно с ключевым словом struct для объявления переменных структурного типа [13.4]. Переменные структуры объявляются так же, как и переменные других типов. На основе вышеприведенного шаблона сделаем несколько объявлений новых структурных переменных: struct employee new_ employee, * employeePtr, stack[120]; В приведенном объявлении new_employee – переменная типа struct employee, *employeePtr – указатель на struct employee, emstack[120] – массив из 120 элементов типа struct employee. Объявленные структурные переменные new_employee, *employeePtr, stack[102] могут быть объединены с определением структуры, а именно: struct employee { char Name [20+1]; // Имя char Surname [20+1]; // Фамилия int age; // возраст double hourlysalary; // почасовой оклад } new_employee, *employeePtr, stack[120]; Имя-этикетка (tag name) не является для структуры обязательным. Если определение структуры не содержит имя-этикетку, то переменные для этой структуры могут быть объявлены только в определении структуры, но не отдельным объявлением [13.1]. При создании структур часто используется ключевое слово typedef (оператор). Оно предоставляет программисту средство для создания синонимов (или псевдонимов) для ранее определенных типов данных. Часто используют typedef для того, чтобы дать укороченное имя структурному типу [13.4]. Например, оператор вида typedef struct card Card; определяет новый тип с именем Card как синоним типа struct card. Ключевое слово typedef может быть использовано в определении типа структуры без имени-этикетки. Например: typedef struct { char *Name; // Имя char *Surname; // Фамилия int age; // возраст double hourlysalary; // почасовой оклад } Man; создает тип Man без использования отдельного оператора typedef. В приведенном примере под символьные указатели необходимо предусмотреть выделение памяти. Созданный тип Man можно использовать для объявления структурных переменных типа struct employee, например: Man stack[120]; Примечание. Использование typedef помогает сделать программу более переносимой. Синтаксис инициализации структур аналогичен инициализации массивов. Например, выполним инициализацию структуры: struct employee { char Name [20+1]; // Имя char Surname [20+1]; // Фамилия int age; // возраст double hourlysalary; // почасовой оклад } new_employee; Возможный вариант инициализации: struct employee { char Name [20]; // Имя char Surname [20]; // Фамилия int age; // возраст double hourlysalary; // почасовой оклад } new_employee = { "Peter", "Smith", 25, 6.78 }; При инициализации структуры структурные элементы (инициализаторы) должны соответствовать заданному типу и отделяться друг от друга запятыми. Доступ к элементам (компонентам, полям) структуры осуществляется двумя способами: с помощью оператора связывающей точки (оператора точки) "." при непосредственной работе со структурой; при использовании указателей на структуры с помощью стрелки "–>".Общий формат доступа к элементам структуры имеет следующий вид: имя_переменной_структуры. имя_поля; имя_указателя_на_структуру–>имя_поля; (*имя_указателя_на_структуру).имя_поля; Например, для вышеприведенной инициализации можно изменить почасовой оклад с помощью оператора точки и указателя на структуру: new_employee. hourlysalary = 21.0; employeePtr –> hourlysalary = 21.0; Следует обратить внимание, что new_employee – это имя всего объекта-структуры, а hourlysalary – имя элемента этой структуры. Аналогично и в случае указателя *employeePtr на структуру. В случае изменения полей структуры следует выделить случай со строками, например: strcpy(new_employee. name, "Stephen"); При этом должен быть предусмотрен заголовочный файл <string. h>. для функции strcpy(). Когда объявлен массив структур, например, stack[120], то это означает, что создано 120 наборов переменных, каждый из которых организован также, как определено в структуре с дескриптором employee. Чтобы получить доступ к определенной структуре stack[120], следует указать имя массива с индексом, который пробегает значения от 0 до 119. Например, для пятой структуры можно сделать изменения в почасовом окладе: stack[4].hourlysalary = 121.5; Как и в других массивах переменных языка С, в массивах структур индексирование начинается с нуля [13.2]. Членами структуры могут быть также массивами или структурами. Когда структура является членом другой структуры, она называется вложенной[13.2]. Рассмотрим следующий пример: struct X = { int A[7][8]; float b; char ch; struct employee Emp2; } Y; Тогда инициализация элементов двухмерного массива А может быть такой: Y. A[2][5] = 99; Инициализация вложенной структуры может быть такой: Y. Emp2.hourlysalary = 12.75; В соответствии со стандартом С89 структуры могут быть вложенными вплоть до 15-го уровня. Стандарт С99 допускает уровень вложенности до 63-го включительно [13.2]. Практическая часть Пример 1. Напишите программу структурного описания каталога одной книги. Программный код решения примера: #include <stdio. h> #include <conio. h> #include <string. h> #define N 40 struct book { // Определение структуры char title[N+1]; // Название книги char author[N+1]; // Автор int year; // Год издания int page; // Количество страниц float price; // Цена в у. е. } Library; int main (void) { // Инициализация полей структуры Library. year = 2007; Library. page = 496; Library. price = 12.78F; strcpy_s(Library. title, N, "Programming in C"); strcpy_s(Library. author, N, "Stephen G. Kochan"); // Вывод на консоль printf("\n\t Title: %s\n", Library. title); printf("\t Author: %s\n", Library. author); printf("\t Year: %d\n", Library. year ); printf("\t Number of pages: %d p.\n", Library. page ); printf("\t Price: %1.2f y. e.\n", Library. price); printf("\n\n Press any key: "); _getch(); return 0; } В программе использованы функции strcpy_s() вместо стандартных функций strcpy(), что позволило избавиться от предупреждений в системе Visual Studio 2008. В случае применения функций strcpy(), их формат записи был бы следующим: strcpy(Library. title, "Programming in C"); strcpy(Library. author, "Stephen G. Kochan"); Результат выполнения программы показан на рис. 13.1.
Задание 1 Совместите объявление и инициализацию структуры. После инициализации структуры выполните изменение ее полей с последующим выводом на консоль. Примените оператор typedef. Поля структуры title и author определите с помощью указателей. Произведите инициализацию структуры после ввода значений полей с клавиатуры. Выполните вывод полей структуры в текстовый файл compX. txt, где Х – номер компьютера, за которым выполняется лабораторная работа.Пример 2. Напишите программу анализа средней успеваемости четырех студентов по четырем предметам за сессию на основе структурного типа данных. Программный код решения примера: #include <stdio. h> #include <conio. h> #include <stdlib. h> #define N 4 // Число студентов #define CH 30 // Число символов для фамилии или имени // Определение структуры struct { char *name;// Имя студента char *surname; // Фамилия студента int M; // Отметка по математическому анализу int A; // Отметка по алгебре int H; // Отметка по истории int In; // Отметка по информатике } student[N]; int main (void) { float mark; int i; // Выделение памяти для символьных указателей for (i = 0; i < N; ++i) { student[i].name = (char *) malloc(CH*sizeof(char)); student[i].surname = (char *) malloc(CH*sizeof(char)); } printf("\n"); for (i = 0; i < N; ++i) { printf("\t Enter a name of %d student \n\t: ", i+1); gets_s(student[i].name, CH); printf("\t Enter a surname of %d student \n\t: ", i+1); gets_s(student[i].surname, CH); printf("\t Enter marks in 4 subjects for %d student (through a blank) \n\t: ", i+1); scanf_s("%i%i%i%i", &(student[i].M),&(student[i].A),&(student[i].H),&(student[i].In)); _flushall(); } puts("\n====================================================================================="); printf("\t Statement of Achievement students:\n"); puts("======================================================================================\n"); for (i = 0; i < N; ++i) { printf("\t %s \t %s:\n\t Mathematical analyses, Algebra, History, Informatics\n \ \n\t\t %d\t\t %3d\t \ %3d\t %5d\n--\n", \ student[i].name, student[i].surname, \ student[i].M, student[i].A, student[i].H, student[i].In); } mark = 0.0F; // F – спецификатор для типа float for (i = 0; i < N; ++i) { mark += (student[i].M + student[i].A + student[i].H + student[i].In); } // Средняя оценка группы из 4 (N)студентов printf("\n\t The average mark groups of %d students: %1.4f", N, mark/(4*N)); // 4*N - общее количество оценок printf("\n\n Press any key: "); _getch(); return 0; } В программе использован массив структур – переменная student[N]. Для определения имени и фамилии предварительно выделяется память для символьных указателей с помощью функций malloc(), для которых включен заголовок #include <stdlib. h>. Шаблон структуры задан без имени-этикетки (без тегового имени). Возможный результат выполнения программы показан на рис. 13.2.
Задание 2 Выполните усреднение оценок для каждого студента. Предусмотрите ввод названий предметов (математический анализ, алгебра и т. д.) и определите их в виде инициализаторов структуры. Увеличьте число студентов до семи и произведите запись результатов в текстовый файл с именем compX. txt, где Х – номер компьютера, за которым выполняется лабораторная работа. В шаблоне структуры задайте теговое имя. Проанализируйте работу программы. Примените оператор typedef для определения структурного типа данных Расположите фамилии (с именем) студентов в порядке возрастания их среднего балла за сессию.Пример 3. Напишите программу создания карточки служащего с помощью структурного типа данных и указателя на структуру. Предусмотрите инициализацию полей структуры и изменения этих полей. Для решения примера воспользуемся структурой, рассмотренной в теоретической части данной лабораторной работы: struct employee { char Name [20+1]; // Имя char Surname [20+1]; // Фамилия int age; // возраст double hourlysalary; // почасовой оклад } employee_new, *PTR; // *PTR – указатель на структуру Программный код решения примера: #include <stdio. h> #include <conio. h> #include <string. h> #include <locale. h> // Предполагаемое число символов в имени или фамилии #define n 20 int main (void) { // Определение структуры struct emloyee { char name[n+1]; // имя char surname[n+1]; // фамилия int age; // возраст double hourlysalary; // почасовой оклад в у. е. } emloyee_new, *PTR; // *PTR - указатель на структуру // Для поддержки русских шрифтов setlocale( LC_ALL, ".1251");// кодовая страница Windows – 1251 PTR = &emloyee_new; //В указатель помещается адрес employee_new // Инициализация полей структуры strcpy_s(PTR -> name, n, "Владимир"); strcpy_s(PTR -> surname, n, "Викулов"); PTR -> age = 25; PTR -> hourlysalary = 6.78; // Вывод на консоль puts("\n=============== Поля структуры ===================="); printf("\n Имя: %s\n Фамилия: %s\n возраст: %d лет\n почасовой оклад: %1.2f y. e.\n", \ PTR -> name, PTR -> surname, PTR -> age, PTR -> hourlysalary); puts("\n==================================================\n"); printf("\n\n Нажмите любую клавишу (Press any key): "); _getch(); return 0; } В программе инициализация полей структуры выполнена с помощью оператора стрелка "–>". С подключением заголовочного файла <locale. h> и определением прототипа функции setlocale(LC_ALL,".1251") производится поддержка русских шрифтов. Результат выполнения программы показан на рис. 13.3.
Задание 3 В программу добавьте нумерацию полей структуры, например: 1) имя: и т. д. В программу введите свое имя и фамилию, возраст и размер получаемой стипендии (если нет, то нуль). Значения новых полей выведите на консоль и в текстовый файл с именем compX. txt, где Х – номер компьютера, за которым выполняется лабораторная работа. Инициализацию полей структуры выполните с помощью указателя. Выполните предварительную инициализацию структуры, а затем смените значения полей. Тип данных double замените на тип floatПример 4. Напишите программу информационной карточки студенческой группы с данными о студентах, применив вложенные структуры. Как известно, если членом структуры является другая структура, то она называется вложенной. Программный код решения примера: #include <stdio. h> #include <conio. h> #include <string. h> #define N 20 int main (void) { struct stud { // шаблон структуры char name[N+1]; // имя студента char surname[N+1]; // фамилия студента int age; // возраст - полных лет double av_mark; // средняя успеваемость }; struct group { // шаблон структуры int number; // номер группы int quantity; // количество студентов в группе struct stud student;// вложенная структура } ACOUY; // ACOUY - структурная переменная // Инициализация полей структуры ACOUY. number = 3; ACOUY. quantity = 21; strcpy_s(ACOUY. student. name, N, "Peter"); strcpy_s(ACOUY. student. surname, N, "Bobrov"); ACOUY. student. age = 20; ACOUY. student. av_mark = 4.25; // Вывод на консоль puts("\n========= Varient field of structure ==============="); printf("\n Group Number: %d,\n The number of students in the group: %d,\n\ Name: %s,\n Surname: %s,\n Age: %d,\n Average mark: %1.2f", \ ACOUY. number, ACOUY. quantity, ACOUY. student. name, ACOUY. student. surname, \ ACOUY. student. age, ACOUY. student. av_mark); puts("\n\n================================================\n"); printf("\n Press any key: "); _getch(); return 0; } Результат выполнения программы показан на рис. 13.4.
Задание 4 Инициализацию структуры выполните с клавиатуры. Предусмотрите массив структур ACOUY[N], где N – количество групп (например, N = 3). Введите данные своей студенческой группы и предусмотрите вывод на консоль данных о себе. Примените оператор typedef и указатель на структуру.Пример 5. Напишите программу составления карточки на студента с динамическим распределением памяти для имени и фамилии на основе структурного типа данных [13.5]. Для динамического распределения памяти применим функцию malloc() и символьные указатели в качестве инициализаторов структуры. Программный код решения примера: #include <stdio. h> #include <conio. h> #include <string. h> #include <stdlib. h> #include <locale. h> const int MAX = 80; // Предполагаемое число символов int main (void) { char *NAME, *SURNAME; struct stud { // шаблон структуры char *name; // указатель вместо массива символов имени студента char *surname; // указатель вместо массива символов фамилии студента int age; // возраст - полных лет float av_mark; // средняя успеваемость int letters; // число символов в имени и фамилии } TABLE, *PTR; PTR = &TABLE; // инициализация указателя на структуру NAME = (char *)malloc(MAX); printf("\n Enter a name of the student: "); gets_s(NAME, MAX); SURNAME = (char *)malloc(MAX); printf(" Enter a surname of the student: "); gets_s(SURNAME, MAX); // Распределение памяти для хранения "структурного" имени PTR->name = (char *)malloc(strlen(NAME)+1); // Копирование имени в распределенную память strcpy_s(PTR->name, strlen(NAME)+1, NAME); // Распределение памяти для хранения "структурной" фамилии PTR->surname = (char *)malloc(strlen(SURNAME)+1); // Копирование фамилии в распределенную память strcpy_s(PTR->surname, strlen(SURNAME)+1, SURNAME); printf(" Enter the age of the student: "); scanf_s("%d", &TABLE. age); printf(" Enter an average mark of the student: "); scanf_s("%f", &TABLE. av_mark); TABLE. letters = strlen(PTR->name) + strlen(PTR->surname); // Для вывода чисел с десятичной запятой setlocale(LC_NUMERIC, ".1251"); puts("\n======== Varient field of structure ==============="); printf("\n Name: %s\n Surname: %s\n Age: %d\n Average mark: %1.2f\n\ The name and surname of student have: %d letters", \ PTR->name, PTR->surname, PTR->age, PTR->av_mark, PTR->letters); puts("\n\n================================================\n"); // Освобождение памяти free(NAME); free(SURNAME); free(PTR->name); free(PTR->surname); printf("\n Press any key: "); _getch(); return 0; } В программе сначала выделяется память для имени и фамилии, для которых использованы указатели *NAME, *SURNAME. После того как имя и фамилия введены с клавиатуры, рассчитывается количество символов плюс символ окончания строки '\0', которые используются для выделения памяти для структурных переменных. Возможный результат выполнения программы показан на рис. 13.5.
Задание 5 С клавиатуры введите данные о себе (буквами русского алфавита), выведите информацию на консоль и в текстовый файл с именем compX. txt, где Х – номер компьютера, за которым выполняется лабораторная работа. Поменяйте операторы стрелка на операторы точка, и наоборот. Вместо функций malloc() примените функции calloc(). Вместо указателей *NAME, *SURNAME примените массивы символов Предусмотрите нумерацию полей структуры, например, 1) Name: Peter и т. д.Пример 6. Напишите программу записи структуры в двоичный файл и чтения ее из двоичного файла. Решение примера разобьем на две части: создадим структуру и произведем пакетную запись в файл этой структуры. Потом операцию записи закомментируем, изменим поля структуры и выполним чтение из файла структуры. Для динамического распределения памяти применим функцию malloc() и символьные указатели в качестве инициализаторов структуры. Программный код решения примера: #include <stdio. h> #include <conio. h> #include <string. h> #define MAX 80 // Предполагаемое число символов // шаблон структуры struct stud { char name[MAX+1]; // массив символов имени студента char surname[MAX+1]; // массив символов фамилии студента char ACOUY[MAX+1]; // специальность int age; // возраст - полных лет float av_mark; // средняя успеваемость }; int main (void) { char NAME[MAX+1], SURNAME[MAX+1], SPEC[MAX+1]; FILE *fid; // условная инициализация переменной структуры и // определение указателя на структуру struct stud TABLE = {"--", "--", "--", 0, 0.0}, *PTR; PTR = &TABLE; // инициализация указателя на структуру printf("\n Enter a name of student: "); gets_s(NAME, MAX); printf(" Enter a surname of student: "); gets_s(SURNAME, MAX); printf(" Enter a speciality: "); gets_s(SPEC, MAX); // Занесение имени в структуру strcpy_s(PTR->name, strlen(NAME)+1, NAME); // Занесение фамилии в структуру strcpy_s(PTR->surname, strlen(SURNAME)+1, SURNAME); // Занесение названия специальности в структуру strcpy_s(PTR->ACOUY, strlen(SPEC)+1, SPEC); printf("Enter the age of the student: "); scanf_s("%d", &TABLE. age); printf(" Enter the average mark student: "); scanf_s("%f", &TABLE. av_mark); puts("\n======= Varient field of structure ============"); printf("\n Name: %s\n Surname: %s\n \ Specialisation: %s\n Age: %d\n Average mark: %0.2f\n ", \ PTR->name, PTR->surname, PTR->ACOUY, PTR->age, PTR->av_mark ); puts("\n================================================\n"); if ( fopen_s(&fid, "D:\\data12.dat", "wb") ) {printf("\n File could not be opened\n"); printf("\n Press any key: "); _getch(); return 0; } //Пакетная запись в двоичный файл fwrite(PTR, sizeof(struct stud), 1, fid); fclose(fid); //if ( fopen_s(&fid, "D:\\data12.dat", "rb") ) //{printf("\n File could not be opened\n"); //printf("\n Press any key: "); //_getch(); return 0; } // Чтение из двоичного файла //fread(PTR, sizeof(struct stud), 1, fid); //puts("\n===== Variants of fields of structure ====="); //printf("\n Name: %s\n Surname: %s\n \ //Specialisation: %s\n Age: %d\n Average mark: %1.2f\n ", \ //PTR->name, PTR->surname, PTR->ACOUY, PTR->age, PTR->av_mark ); //puts("\n===============================================\n"); //fclose(fid); printf("\n\n Press any key: "); _getch(); return 0; } Форматы записи в двоичный файл и чтения из двоичного файла: fwrite(PTR, sizeof(struct stud), 1, fid); fread(PTR, sizeof(struct stud), 1, fid); В функции fwrite() первый параметр PTR определяет собой содержимое структуры (данных в других случаях), которое по указателю *fid на файл записывается в файл. Второй параметр sizeof(struct stud) определяет собой размер в байтах. Третий параметр 1 – это количество блоков, которое будет записываться в файл. При этом второй и третий параметры перемножаются, поэтому их можно поменять местами. В программе чтение из двоичного файла закомментировано. Для корректной работы программы сделана условная инициализация полей структуры, чтобы прописать адрес структуры в памяти с данными. Возможный вариант выполнения программы при записи информации в двоичный файл показан на рис. 13.6. После комментирования программного кода с записью в файл и снятия комментариев к программному коду чтения из файла результат выполнения программы показан на рис. 13.7.
Задание 6 Перезапишите информацию из файла data12.dat в двоичный файл с именем compX. txt, где Х – номер компьютера, за которым выполняется лабораторная работа. Символьные массивы полей структуры задайте с помощью указателей. Запись данных произведите в двоичный файл с различными расширениями: .txt, .bin, .doc. Определите размеры этих файлов в байтах. Напишите программу записи массива структур в двоичный файл и чтения из него всей структуры с выводом результатов на консоль.Контрольные вопросы Как определяется структура в языке С? Как объявляется структура в языке С? Какими способами можно объявить новые структурные переменные? Какие форматы используются для доступа к элементам структуры? Что такое вложенная структура? Какой уровень вложенности структур поддерживается стандартом С89? Как объявляется массив структур? Как объявляется указатель на структуру? Как инициализируется указатель на структуру? Как осуществляется инициализация полей структуры, определенных как символьные массивы? Какой оператор может быть использован для определения структурного типа данных? Является ли тег структуры именем ее типа? | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Программирование на языке C в Microsoft Visual Studio 2010 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
14. Лекция: Объединения и перечислимые типы в языке С: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Теоретическая часть 14.1. Объединения Объединение (union) – это тип, который позволяет хранить различные типы данных в одном и том пространстве памяти (но не одновременно)[14.1]. Объединения образуются во многом подобно структурам. Существуют шаблоны объединений и переменные типа объединения. Они могут быть определены с помощью одного или двух действий, причем в последнем случае используется дескриптор объединения [14.1]. Пример шаблона объединения с дескриптором hold: union hold { int digit; double bigf; char letter; }; Объединение может хранить значение типа int, или double, или char. Структура с такими же полями способна хранить все типы одновременно. Пример определения трех переменных объединения: union hold fit; // переменная объединения типа hold union hold save[10];// массив из 10 переменных объединения union hold *ptr; // указатель на переменную типа hold Первое объявление создает единственную переменную fit. Компилятор выделяет пространство памяти, достаточное для того, чтобы хранить наибольшую из описанных вариантов, а именно тип double, который требует для себя обычно 8 байт. Второе объявление save[10] создает массив с 10 элементами, каждый из которых имеет размер в 8 байт. Третье объявление создает указа-тель, который может содержать адрес объединения hold. Рассмотрим варианты инициализации объединения [14.1]: union hold valA;// создали переменную valA по шаблону union hold valA. letter = 'R'; union hold valB = valB;// инициализация одного объединения другим union hold valC = {88};// инициализация числового элемента Рассмотрим варианты использования объединения: union hold fit; fit. digit = 23; // Число 23 хранится в переменной fit, 2 байта fit. bigf = 6.78; // Число 23 затерто, хранится 6.78, 8 байтов fit. letter = 'h';// Число 6.78 затерто, хранится символ h,1 байт Операция точки показывает, какой тип данных используется в текущий момент [14.1]. За один раз запоминается только одно значение. Нельзя одновременно хранить значение типа char и значение типа int, даже если для этого имеется достаточно пространства (памяти для 8 байт). Следить за тем, какие значения на текущий момент хранятся в объединении, входит в обязанности программиста. Бывает, что на различных этапах выполнения программы одни переменные могут быть не нужны, в то время как другие, наоборот, используются только в текущей части программы, поэтому объединения экономят пространство, вместо того чтобы впустую тратить память на не использующиеся в данный момент переменные. При инициализации полей объединения вместо операции точки можно использовать операцию стрелки, если используется указатель на объединение. В частности, для рассмотренных примеров: int x; ptr = &fit; x = ptr->digit; В содержимом объединения (в качестве инициализаторов) могут быть структуры. При этом по правилам использования объединения обращаться можно будет только к одной из структур. Фактически объединение является структурой, в которой все элементы имеют нулевое смещение от ее начала. Она имеет достаточную длину, чтобы в нее поместился самый длинный элемент, и при этом выравнивание выполняется правильно для всех типов данных в объединении [14.2]. Над объединениями разрешено выполнять те же операции, что и над структурами: присваивать или копировать как единое целое, брать адрес и обращаться к отдельным элементам. Объединения могут употребляться в структурах и массивах и наоборот. Способ обращения к члену объединения в структуре (или к члену структуры в объединении) полностью идентичен обращению к элементу вложенной структуры. Объединение можно инициализировать только данными того типа, который имеет его первый элемент [14.2]. Смысловое отличие объединения от структуры состоит в том, что записать информацию в объединение можно с помощью одного из его элементов, а выбрать данные из того участка памяти можно с помощью другого элемента того же объединения [14.3]. К объединениям может быть применен оператор typedef, после чего можно вводить обозначения объединяющих типов, не требующие применения служебного слова union. Рассмотрим пример: typedef union data { char str[79+1]; int a; double x; } new_data; Определения новых переменных (например, student1, student2) объединений будут выглядеть таким образом: new_data student1, student2; Объединения не относятся ни к скалярным данным, ни к данным агрегирующих типов [14.3]. Объединения не могут сравниваться операциями "==" и "!=" по тем же самым причинам, что и структуры, поскольку элементы объединения не обязательно хранятся в последовательных байтах памяти. Объединения часто используются для специального преобразования типов, поскольку к хранящимся в объединении данным можно обращаться разными способами [14.4]. 14.2. Перечислимые типы Перечислимый (enumerated) и тип служит для объявления символических имен, представляющих целочисленные константы [14.1]. Можно сказать, что enumerated type (перечислимый тип) – это тип данных, заданных списком принадлежащих ему значений. Назначение перечислимых типов заключается в том, чтобы повысить удобочитаемость программы [14.1]. Синтаксис в этом случае аналогичен синтаксису, который используется для описания структур. Примеры объявления перечислимого типа: enum spectrum {red, orange, yellow, green, blue, violet}; enum spectrum color; Первое объявление устанавливает spectrum как имя дескриптора, который позволяет использовать enum spectrum в качестве имени типа. Второе объявление делает color переменной этого типа. Идентификаторы, заключенные в фигурные скобки, перечисляют возможные значения, которые может принимать переменная spectrum. Соответственно, возможными значениями color являются red, orange, yellow и т. д. Но эти возможные значения являются целочисленными, т. е. 0, 1, 2, 3, 4, 5. Другими словами, значения в enum начинаются с 0, если не задано иное число, и всегда увеличиваются на 1. В общем случае перечислимые константы имеют тип int, но перечислимые переменные не так жестко привязаны к целочисленному типу данных, поскольку этот тип может содержать перечислимые константы. Например, перечислимые константы переменной spectrum имеют диапазон 0 ... 5 (как в массиве), поэтому компилятор может выбрать тип unsigned char для представления переменной color. В языке программирования С к перечислимой переменной можно применять операции инкрементирования "++" и декрементирования "––". Например, for (color = red; color <= violet; ++color) ...; По умолчанию константам в перечислимом списке присваиваются целые значения 0, 1, 2 и так далее. В то же время возможны и присваиваемые значения, например: enum levels {low = 100, medium = 500, high = 2000}; Если назначить конкретное значение одной из констант, то все следующие константы будут пронумерованы последовательно в возрастающем порядке, например: enum feline {cat, lynx = 10, puma, tiger}; В этом случае cat (кошка) получает значение 0 по умолчанию, lynx (рысь), puma (пума), tiger (тигр), соответственно, получают значения 10, 11, 12. Перечисления особенно полезны там, где не требуется преобразования значений (целого типа) в имена (массив символов). В частности, перечисления часто используются в компиляторах для создания таблицы соответствия символов [14.4]. Практическая часть Пример 1. Для переменной типа объединения предусмотрите ввод, и вывод элементов ее полей. Программный код решения примера: #include <stdio. h> #include <conio. h> #include <string. h> #define N 79 union hold { char str[N+1]; double bigf; char ch; int digit; }; int main (void) { double D2; int i = 0, digit2; char str2[80], ch2; union hold fit, *PTR = &fit; //PTR = &fit; // вариант взятия адреса printf("\n\t Fields of the \"union\":\n \ 1) string, 2) double, 3) character, 4) integer\n"); do { printf("\n Enter %d field of the \"union\": ", i+1); _flushall(); if (i == 0) { gets_s(str2, N); strcpy_s(PTR->str, strlen(str2) + 1, str2); printf(" The first field: %s\n", PTR->str); i++; } else if (i == 1) { scanf_s("%lf", &D2); printf(" The second field: %1.4f\n", PTR->bigf = D2); i++; } else if (i == 2) { scanf_s("%c", &ch2); printf(" The third field: %c\n", PTR->ch = ch2); i++; } else { scanf_s("%d", &digit2); printf(" The fourth field: %d\n", PTR->digit = digit2); i++; } } while (i < 4); printf("\n 1 field: %s\n 2 field: %1.4f\n \ 3 field: %c\n 4 field: %d\n", \ PTR->str, PTR->bigf, PTR->ch, PTR->digit); printf("\n Press any key: "); _getch(); return 0; } Результат выполнения программы показан на рис. 14.1.
Как видно из результата выполнения программы, заполнение четырех полей объединения возможно поочередно. После "прохода" всех полей строчные поля не сохранились. Задание 1 В программе переставьте очередность полей объединения. Проанализируйте результат выполнения измененной программы. Определите размерность объединения с помощью оператора sizeof(). Введите свою фамилию, средний балл за последнюю сессию, начальную букву своего имени и год своего рождения. Вместо операции "стрелка" примените операцию "точка". С помощью оператора typedef создайте переменную объединения с именем compX, где Х – номер компьютера, за которым выполняется лабораторная работа.Пример 2. Напишите программу, в которой информация о геометрических фигурах представляется на основе комбинированного использования переменных типа структуры и объединения. В качестве фигур примите эллипс с заданными полуосями, окружность с заданным радиусом и координатами центра, равнобочную трапецию с заданными основаниями и боковыми сторонами. Общие компоненты фигур: площадь фигур, периметр трапеции, длина окружности, длина эллипса. Программный код решения примера: #include <stdio. h> #include <conio. h> // #define?_USE_MATH_DEFINES #include <math. h> const double pi = 3.L; int main (void) { double Ltr, AK; struct figure { double area, perimeter; int type; union select { double R[3]; // circle double E[2]; // ellipse double Trap[3]; //trapezium } geom_fig; } geom, *PTR = &geom; printf("\n Figures: 1 - Circle, 2 - Ellipse, 3 - trapezium"); printf("\n\n Select figure: "); scanf_s("%d", &(PTR->type)); switch (PTR->type) { case 1: PTR->geom_fig. R[0] = 5.0L; // радиус круга PTR->geom_fig. R[1] = 1.5L; // x0 PTR->geom_fig. R[2] = 2.5L; // y0 PTR->area = pi*(PTR->geom_fig. R[0])*(PTR->geom_fig. R[0]); PTR->perimeter = 2*pi*(PTR->geom_fig. R[0]); printf("\n %d) Circle:\n R = %1.4f, \ x0 = %1.4f, y0 = %1.4f, area = %1.4f, L = %1.4f\n", \ PTR->type, PTR->geom_fig. R[0], PTR->geom_fig. R[1], PTR->geom_fig. R[2], \ PTR->area, PTR->>perimeter); break; case 2: PTR->geom_fig. E[0] = 5.0L; // a - большая полуось PTR->geom_fig. E[1] = 4.0L; // b - малая полуось PTR->area = pi*(PTR->geom_fig. E[0])*(PTR->geom_fig. E[1]); PTR->perimeter = 2.0*pi*(PTR->geom_fig. E[0])*\ (PTR->geom_fig. E[0] - PTR->geom_fig. E[1])/(PTR->geom_fig. E[0]); printf("\n %d) - Ellipse:\n a = %1.4f, b = %1.4f, \ area = %1.4f, L = %1.4f\n", \ PTR->type, PTR->geom_fig. E[0], PTR->geom_fig. E[1], \ PTR->area, PTR->perimeter); break; case 3: PTR->geom_fig. Trap[0] = 12.0L;// AD PTR->geom_fig. Trap[1] = 7.0L; // BD PTR->geom_fig. Trap[2] = 5.5L; // h AK = (PTR->geom_fig. Trap[0] - PTR->geom_fig. Trap[1])/2.0; Ltr = (PTR->geom_fig. Trap[0] + PTR->geom_fig. Trap[1]) + \ 2.0*sqrt(AK*AK + (PTR->geom_fig. Trap[2])*(PTR->geom_fig. Trap[2]) ); PTR->area = 0.5*(PTR->geom_fig. Trap[0] + PTR->geom_fig. Trap[1])*\ (PTR->geom_fig. Trap[2]); PTR->perimeter = Ltr; printf("\n %d) Trapezium:\n AD = %.4f, BC = %.4f, h = %.4f, \ Area = %1.4f, L = %1.4f\n", \ PTR->type, PTR->geom_fig. Trap[0], PTR->geom_fig. Trap[1], \ PTR->geom_fig. Trap[2], PTR->area, PTR->perimeter); break; default : printf("\n\t Error! Break\n"); break; } printf("\n Press any key: "); _getch(); return 0; } В программе одним из полей структуры geom является объединение. В зависимости от выбора типа – метки активного элемента type – происходит вывод на консоль параметров фигуры, ее площади и длины граничной линии (периметр трапеции и т. д.). Компонент type используется для указания, какой из компонентов (полей) объединения geom_fig является активным в данный момент. Подобную структуру называют переменной структурой. Определение числа Результат выполнения программы показан на рис. 14.2.
Задание 2 Выполните программу для всех возможных случаев. По программному коду запишите формулы для расчета площадей и длин граничных линий каждой из фигур. Предусмотрите ввод с клавиатуры параметров выбранной фигуры.Пример 3. Напишите программу вывода на консоль дня недели до и после заданного номера дня недели с помощью перечислимого типа данных enum. Программный код решения примера: #include <stdio. h> #include <conio. h> const int NUMDAYS = 7; enum DAYS { Mon, // Monday, Tue, // Tuesday, Wed, // Wednesday, Thu, // Thursday, Fri, // Friday, Sat, // Saturday, Sun // Sunday } day1, day2, day3; // сегодня, вчера, завтра // Прототипы функций int day_before(DAYS); int day_after(DAYS); void print_day(DAYS); int main (void) { printf("\n Days of week:\n 1) Monday, 2) Tuesday, 3) Wednesday, \ 4) Thursday,\n 5) Friday, 6) Saturday, 7) Sunday\n"); printf("\n Select the number of days a week: "); scanf_s("%d", &day1); if (day1 > 7 || day1 < 1) printf("\n\t Error!\n"); else { day2 = day_before(day1); day3 = day_after(day1); printf("\n If today "); print_day(day1); printf("\n yesterday was "); print_day(day2); printf("\n and tomorrow will be "); print_day(day3); } printf("\n\n Press any key: "); _getch(); return 0; } // Описание функции int day_after(day) { int aft, ex; aft = (day+1) % NUMDAYS; // Остаток от деления по модулю if (aft == 0) ex = NUMDAYS; else ex = aft; return ex; } // Описание функции int day_before(day) { int pre, ex; pre = (day-1) % NUMDAYS; if (pre == 0) ex = NUMDAYS; else ex = pre; return ex; } // Описание функции void print_day(day) { int day_i = day-1; // Массив указателей static char *days[] = char *days[] = {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" }; printf(" %s\n", days[day_i]); } В программе использован спецификатор класса памяти static для массива указателей, инициированных названиями дней недели. В таком случае массив указателей с заданным именем будет не доступен за пределами функции, где он определен. Для данной программы класс памяти static внутри функции print_day() инициирует выделение памяти для объекта (массив указателей) и служит определением массива указателей. Возможный результат выполнения программы показан на рис. 14.3.
Задание 3 В программе используйте кодовые страницы (Windows или DOS) для работы с русскими шрифтами (с подключением заголовка <locale. h>). Видоизмените программу для выбора месяца года (с учетом п.1). Видоизмените программу для выбора одного из 7 цветов (Каждый Охотник Желает Знать, Где Сидят Фазаны – красный, оранжевый, желтый, зеленый, голубой, синий, фиолетовый) с выводом на консоль цвета и его кода RGB (Red, Green, Blue – красный, зеленый, синий). Выполните изменения в программе, чтобы выбор дня недели осуществлялся при инициализации переменной day1, например, day1 = Sun. Напишите программу вывода на консоль названия месяца до и после заданного (введенного пользователем) номера месяца года с помощью перечислимого типа данных enum.Пример 4. Напишите программу вывода количества дней для каждого месяца не високосного года на основе перечислимого типа данных [14.5]. Программный код решения примера: #include <stdio. h> #include <conio. h> int main (void) { enum month {january = 1, february, march, april, may, june, july, august, september, october, november, december } aMonth; int days; printf("\n Enter the number of the month: "); scanf_s("%d", &aMonth); switch (aMonth) { // 31 день case january: case march: case may: case july: case august: case october: case december: days = 31; break; // 30 дней case april: case june: case september: case november: days = 30; break; case february: days = 28; break; default: printf("\n\t Wrong number of the month\n"); days = 0; break; } if (days) printf("\n The number of days of the month: %d\n", days); if (aMonth == february) printf("\n... or 29 if it is a leap year\n"); printf("\n\n Press any key: "); _getch(); return 0; } Возможный результат выполнения программы показан на рис. 14.4.
Задание 4 Произведите запись в текстовый файл с именем compX. txt, где Х – номер компьютера, за которым выполняется лабораторная работа. Создайте переменную перечислимого типа compX, где Х – номер компьютера, за которым выполняется лабораторная работа. Предусмотрите вывод названия месяца и количества его дней. Предусмотрите количество дней в феврале для високосного года (без добавления разделительного союза "или").Контрольные вопросы Какие отличия и общие черты имеются у структур и объединений? Для чего используются объединения в языке С? Как они определяются? Как осуществляется инициализация полей объединения? Как можно вывести значения полей объединения на дисплей? Для чего служит перечислимый тип данных в языке С? Как он определяется? Какие операции разрешено выполнять над объединениями? Какие значения по умолчанию присваиваются полям перечислимого списка? | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Программирование на языке C в Microsoft Visual Studio 2010 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
15. Лекция: Структуры и функции языка С: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Теоретическая часть Разрешенными операциями над структурами являются копирование или присваивание структуры как целого, взятие ее адреса операцией &, а также обращение к ее элементам [15.1]. Копирование и присваивание включает в себя также передачу аргументов в функции и возвращение значений из функций. Возможна передача членов структур функциям и передача целых структур функциям. При передаче функции члена структуры передается его значение, притом не играет роли то, что значение берется из члена структуры. Например [15.2], пусть задана структура следующего вида: struct fred { char x; int y; float z; char str[10]; // с учетом символа окончания строки } mike; Тогда каждый член этой структуры можно передать функции, например, func(mike. x); // передается символьное значение х func2(mike. y);// передается целое значение y func3(mike. z);// передается значение с плавающей точкой z func4(mike. str);// передается адрес строки str[10] func(mike. str[2]);// передается символьное значение str[2] Если же нужно передать адрес отдельного члена структуры, то перед именем структуры должен находиться оператор &. Для рассмотренных примеров будем иметь func(&mike. x); // передается адрес символа х func2(&mike. y);// передается адрес целого y func3(&mike. z);// передается адрес члена z с плавающей точкой func4(mike. str);// передается адрес строки str func(&mike. str[2]);// передается адрес символа в str[2] Когда в качестве аргумента функции используется структура, для передачи целой структуры используется обычный способ вызова по значению [15.2]. Это означает, что любые изменения в содержимом параметра внутри функции не отразятся на той структуре, которая передана в качестве аргумента. При использовании структуры в качестве параметра надо помнить, что тип аргумента должен соответствовать типу параметра. В качестве примера рассмотрим следующий программный код [15.2]: #include <stdio. h> #include <conio. h> // Определение глобального типа структуры struct struct_type { int a, b; char ch; }; // Прототип функции void fun(struct struct_type parm); int main (void) { struct struct_type arg; arg. a = 1000; fun(arg); printf("\n Press any key: "); _getch(); return 0; } // Определение функции void fun(struct struct_type parm) { printf("\n %d\n", parm. a); } Назначение этой программы состоит в печати заданных полей структуры, которая объявлена как глобальная, чтобы структурный тип был виден во всей программе. Когда используется указатель на структуру, вместо оператора точки используется оператор "стрелка". Видоизменим рассмотренную программу, введем в нее указатель на структуру: #include <stdio. h> #include <conio. h> // Определение глобального типа структуры struct struct_type { int a, b; char ch; }; // Прототип функции void fun(struct struct_type *parm); int main (void) { struct struct_type arg,*PTR; PTR = &arg; PTR->a = 999; fun(PTR); printf("\n Press any key: "); _getch(); return 0; } // Определение функции void fun(struct struct_type *parm) { printf("\n %d\n", parm->a); } Современные компиляторы языка С позволяют передавать структуры в качестве аргументов функций. Изменим вышеприведенную программу так, чтобы она давала возможность печатать сумму двух целых чисел и заданный символ: #include <stdio. h> #include <conio. h> // Определение глобального типа структуры struct struct_type {int a, b; char ch; }; // Прототип функции void fun(struct struct_type STRUCT3); int main (void) { struct struct_type struct2 = {2, 3, 'Z' }; fun(struct2); printf("\n Press any key: "); _getch(); return 0; } // Определение функции void fun(struct struct_type STRUCT3) { printf("\n %c: %d + %d = %d\n", \ STRUCT3.ch, STRUCT3.a, STRUCT3.b, STRUCT3.a + STRUCT3.b); } В современных версиях языка С, в том числе и ANSI C, структуры не только можно передавать функции в качестве аргументов, но и возвращать в качестве возвращаемого значения [15.3]. Использование структур в качестве аргументов функции позволяет передавать функции информацию о структуре. Использование функций для возврата структур позволяет передавать информацию из вызываемой функции в вызывающую функцию. Указатели на структуры допускают также двусторонний обмен данными. Практическая часть Пример 1. Напишите программу передачи частей структуры в качестве аргументов функции. Рассмотрим пример подсчета суммы двух вещественных чисел, которые вводятся с клавиатуры для двух пользователей, которые задаются своими именами. Программный код решения примера: #include <stdio. h> #include <conio. h> #define MAX 20 // Шаблон структуры struct test {char A[MAX+1]; double ax; char B[MAX+1]; double by; }; //Прототип вспомогательной функции double sum(double, double); // Главная функция int main (void) { // Создание структурной переменной struct test AB; //Заполнение полей структуры printf("\n\t Type 1 st name: "); gets_s(AB. A, MAX); printf("\t Enter the first real number: "); scanf_s("%lf", &AB. ax); _flushall(); printf("\n\t Enter 2 nd name: "); gets_s(AB. B, MAX); printf("\t Enter the second real number: "); scanf_s("%lf", &); printf("\n\t The sum of two numbers \ %1.2f and %1.2f, %s and %s: %1.2f\n", AB. ax, , AB. A, AB. B, sum(AB. ax, )); printf("\n Press any key: "); _getch(); return 0; } // Функция суммирования двух чисел double sum(double x, double y) { return (x + y); } Возможный результат выполнения программы показан на рис. 15.1.
Задание 1 Одним из вводимого имени примите свое имя (имя пользователя). Предусмотрите предварительный подсчет суммы двух чисел по возвращаемому значению функции sum(). Произведите инициализацию структуры в программе и передайте ее вещественные данные в функцию для подсчета их суммы. Напишите функцию, аргументом которой будет вычисленная сумма двух чисел – полей структуры, чтобы эта функция выполняла вывод на консоль полученной суммы. В программе операцию "точка" замените на операцию "стрелка".Пример 2. Напишите программу выполнения арифметических действий с комплексными числами на основе структурного типа данных и печати результатов выполненных действий с помощью вспомогательной функции. Для решения данного примера следует указать на действия с комплексными числами, заданными в алгебраической форме [15.4]. Суммой двух комплексных чисел
Правило сложения. При сложении комплексных чисел складываются действительные и мнимые части соответственно. Разностью чисел Правило вычитания. При нахождении разности
Произведением двух комплексных чисел
Правило умножения. Комплексные числа перемножаются как двучлены, при этом учитывается: Частным от деления числа Правило деления. Чтобы разделить число Комплексные числа называются сопряженными, если у них равны действительные части, а мнимые противоположны по знаку. Программный код решения примера: #include <stdio. h> #include <conio. h> #include <locale. h> // Шаблон структуры struct comp { long double Re; long double Im; }; // Прототип функции с аргументами: // структура и симольная переменная void complex (struct comp ri[2], char op); int main (void) { long double x, y; char op; // Определение структурной переменной struct comp ri[2]; setlocale(LC_ALL, ".1251"); // для русских шрифтов //Для определения чисел с десятичной точкой setlocale(LC_NUMERIC, "English"); printf("\n\t ДЕЙСТВИЯ С КОМПЛЕКСНЫМИ ЧИСЛАМИ\n"); printf("\n Введите действительную часть 1-го комплексного числа: "); scanf_s("%lf", &x); _flushall(); ri[0].Re = x; printf(" Введите мнимую часть 1-го комплексного числа: "); scanf_s("%lf", &y); _flushall(); ri[0].Im = y; printf("\n Введите действительную часть 2-го сомплексного числа: "); scanf_s("%lf", &x); _flushall(); ri[1].Re = x; printf(" Введите мнимую часть 2-го комплексного числа: "); scanf_s("%lf", &y); _flushall(); ri[1].Im = y; printf("\n Введите арифметический оператор: "); scanf_s("%c", &op); _flushall(); //Вызов функции расчета комплексных чисел printf("\n Результат действия (\"%c\") над двумя комплексными числами\n (результат с десятичной запятой):", op); // для русских шрифтов с числами с десятичной запятой setlocale(LC_ALL, ".1251"); complex (ri, op); //Для определения числа с десятичной точкой setlocale(LC_NUMERIC, "English"); //Вызов функции расчета комплексных чисел printf("\n Результат действия (\"%c\") над двумя комплексными числами\n (результат с десятичной точкой):", op); complex (ri, op); printf("\n Нажмите любую клавишу (Press any key): "); _getch(); return 0; } // Вспомогательная функция void complex (struct comp ri[2], char z) { // Вспомогательные переменные double num1, num2, den; // Выбор арифметического действия switch (z) { case '+' : if ((ri[0].Im + ri[1].Im) >= 0) printf("\n\t %1.4f + %1.4fi\n", ri[0].Re + ri[1].Re, ri[0].Im + ri[1].Im); if ((ri[0].Im + ri[1].Im) < 0) printf("\n\t %1.4f - %1.4fi\n", ri[0].Re + ri[1].Re, -(ri[0].Im + ri[1].Im)); break; case '-' : if ((ri[0].Im - ri[1].Im) >= 0) printf("\n\t %1.4f + %1.4fi\n", ri[0].Re - ri[1].Re, ri[0].Im - ri[1].Im); if ((ri[0].Im - ri[1].Im) < 0) printf("\n\t %1.4f - %1.4fi\n", ri[0].Re - ri[1].Re, -(ri[0].Im - ri[1].Im)); break; case '*' : if ((ri[0].Re*ri[1].Im + ri[1].Re*ri[0].Im) >= 0) printf("\n\t %1.4f + %1.4fi\n",\ ri[0].Re * ri[1].Re - ri[0].Im*ri[1].Im, ri[0].Re*ri[1].Im + ri[1].Re*ri[0].Im); if ((ri[0].Re*ri[1].Im + ri[1].Re*ri[0].Im) < 0) printf("\n\t %1.4f - %1.4fi\n",\ ri[0].Re * ri[1].Re - ri[0].Im*ri[1].Im, -(ri[0].Re*ri[1].Im + ri[1].Re*ri[0].Im)); break; case '/' : if (ri[1].Re!= 0 && ri[1].Im!= 0) { den = ri[1].Re*ri[1].Re + ri[1].Im*ri[1].Im; num1 = (ri[0].Re*ri[1].Re - ri[0].Im*(-ri[1].Im))/den; num2 = (ri[0].Re*(-ri[1].Im) + ri[1].Re*ri[0].Im)/den; if (num2 >= 0) printf("\n\t %1.4f + %1.4fi\n",num1, num2); if (num2 < 0) printf("\n\t %1.4f - %1.4fi\n",num1, - num2); } else printf("\n\t Ошибка! Деление на нуль.\n"); break; default : printf("\n\t Ошибка! Неизвестный оператор.\n"); break; } } Возможный результат выполнения программы показан на рис. 15.2.
Задание 2 Дополните программу вычислением модуля комплексного числа, полученного в результате произведенного арифметического действия. Консольный вывод запишите в текстовый файл с именем compX. txt, где Х – номер компьютера, за которым выполняется лабораторная работа. Напишите программу ввода комплексных чисел в виде одной строки, например, (4+5.5i) – первое вводимое число как строка, (2–3.3i) – второе вводимое число как строка. Предусмотрите также арифметические действия над комплексными числами. Напишите программу возведения комплексного числа в степень (см. формулу бинома Ньютона).Пример 3. Напишите программу имитатора случайного таймера с помощью указателей на структуру [15.2]. Программный код решения примера: #include <stdio. h> #include <conio. h> #include <time. h> #include <stdlib. h> // Глобальный шаблон структуры struct mtime { int hours; int minutes; int seconds; }; // Прототипы вспомогательных функций void update (struct mtime *t); void display (struct mtime *t); // Главная функция int main (void) { int i; //Создание переменной структурного типа struct mtime systime; // Для изменения псевдослучайной последовательности srand((unsigned) (long)time(NULL)); // Начальная инициализация структурной переменной systime. hours = 0; systime. minutes = 0; systime. seconds = 0; // Цикл вывода расчетного времени for (i = 0; i < 10; ++i) { update(&systime); display(&systime); } printf("\n\n Press any key: "); _getch(); return 0; } // 1-я вспомогательная функция void update (struct mtime *t) { // Заполнение полей структуры случайными числами t -> hours = (int)(24*rand()/RAND_MAX); t -> minutes = (int)(60*rand()/RAND_MAX); t -> seconds = (int)(60*rand()/RAND_MAX); // Условия расчетного времени t -> seconds += 1; if (t -> seconds == 60) { t -> seconds = 0; t -> minutes++; } if (t -> minutes == 60) { t -> minutes = 0; t -> hours++; } if (t -> hours == 24) t -> hours = 0; } // 2-я вспомогательная функция void display (struct mtime *t) { printf("\n System time: "); printf("%02d:", t -> hours); printf("%02d:", t -> minutes); printf("%02d\n", t -> seconds); } В программе аргументами функций update() и display() являются указатели на структуру с дескриптором (этикеткой) mtime. При этом в главной функции main() создается структурная переменная systime по шаблону struct mtime и она передается в функции update() и display() через свой адрес, т. е. &systime. Так происходит 10 раз (в цикле). Примечание. В прототипе функций указатели на структуру могут быть записаны в обезличенной форме, например: void update (struct mtime *); void display (struct mtime *); Возможный результат выполнения программы показан на рис. 15.3
Задание 3 Проверьте работу программы без начальной инициализации полей структурной переменной systime. Результат выполнения программы запишите в текстовый файл с именем compX. txt, где Х – номер компьютера, за которым выполняется лабораторная работа. В программу добавьте нумерацию итераций цикла, т. е. 1), 2), ..., 10). Напишите программу по вводу своей фамилии, имени и подсчета в них количества букв. При этом создать шаблон структуры с полями для имени, фамилии и количества букв. Ввод имени и фамилии должна выполнять одна функция, подсчет количества букв должна выполнять вторая функция, печать результата должна выполнять третья функция. Для связи между функциями примените указатели на структуру.Пример 4. Напишите программу, в которой используются функции, имеющие на входе структуру и возвращающие структуру. Напишем программу расчета средней успеваемости студента за последнюю экзаменационную сессию (или другим экзаменам) с использованием структурного типа данных. Программный код решения примера: #include <stdio. h> #include <conio. h> #define MAX 79 // Шаблон структуры struct table { char name[MAX+1]; char surname[MAX+1]; char subject_1[MAX+1]; char subject_2[MAX+1]; char subject_3[MAX+1]; char subject_4[MAX+1]; int mark[4]; float mean; } student = { // Инициализация структурной переменной "Peter", "Bobrov", "Mathematics", "Informatics", "Programming", "Physics", {0,0,0,0}, 0.0f }; // Прототип вспомогательной функции struct table infor(struct table student); // Главная функция int main (void) { // Объявление и инициализация структурной переменной struct table infor2 = {" "," "," "," "," "," ", {0,0,0,0},0.0f}; // Присвоение структуры от функции infor2 = infor(student); printf("\n The level of knowledge a student of %s %s is: \ %1.2f\n", infor2.name, infor2.surname, infor2.mean); printf("\n\n Press any key: "); _getch(); return 0; } // Вспомогательная функция с аргументом структурного типа // и возвращающая структуру struct table infor(struct table student) { float x = 0.0f; int i; printf("\n\t Enter a name: "); gets_s(student. name, MAX); printf("\t Enter a surname: "); gets_s(student. surname, MAX); printf("\t Enter 1-st academic subject: "); gets_s(student. subject_1, MAX); printf("\t Enter a mark in the first subject: "); scanf_s("%d",&student. mark[0]); _flushall(); printf("\t Enter 2-nd academic subject: "); gets_s(student. subject_2, MAX); printf("\t Enter a mark in the second subject: "); scanf_s("%d",&student. mark[1]); _flushall(); printf("\t Enter 3-rd academic subject: "); gets_s(student. subject_3, MAX); printf("\t Enter a mark in the third subject: "); scanf_s("%d",&student. mark[2]); _flushall(); printf("\t Enter 4-th academic subject: "); gets_s(student. subject_4, MAX); printf("\t Enter a mark in the fourth subject: "); scanf_s("%d",&student. mark[3]); // Расчет средней оценки по 4 предметам for (i = 0; i < 4; ++i) { x += student. mark[i]; student. mean = x/4; } // Возвращение структурной переменной return (student); } В программе приведена инициализация структурной переменной student для наглядности. Инициализация переменной типа float выполнена с суффиксом f. Присвоение одной структурной переменной другой может быть только в случае, когда они принадлежат одному и тому же шаблону структур. Функции _flushall() введены для того, чтобы устранить пустую строку перед использованием функции gets_s() (или gets()) после ввода с помощью функции scanf_s (или scanf()). Возможный результат выполнения программы показан на рис. 15.4.
Пример 5. Напишите программу, в которой функция возвращает указатель на структуру при поиске служебных слов языка С, вводимых с клавиатуры пользователем [3;8]. Программный код решения примера: #include <stdio. h> #include <conio. h> #include <string. h> #define MAX 1000 // Функция ввода строки с клавиатуры void getLine(char str[], int m) { int c, i; for (i = 0; i < m-1 && (c = getchar())!= EOF && c!= '\n'; i++) str[i] = c; // После цикла str[i] = '\0'; // символ окончания строки } // Создание структуры глобального типа struct key { char *keyword; //int keycount; } tab[] = { "for", "while", "do", "if", "else", "switch", "case", "break", "default" }, *bam;// указатель на структуру // Вспомогательная функция // с указателем на структуру key struct key *PTR_ANALYSE(char *word, struct key tab[], int n) { int i; struct key *PTR; for (i = 0; i < n; ++i) if (strcmp(tab[i].keyword, word) == 0) { PTR = &tab[i]; // возвращение указателя на структуру типа key return (PTR); } return NULL; // служебное слово не найдено } // Главная функция int main (void) { int c; char str[MAX]; printf("\n The analysis of input of syntactic words\n of the programming language C\n\n"); printf("\n The end of the session: press Ctrl+Z after pressing Enter\n\n"); do { printf("\t Enter a new line: "); getLine(str, MAX); printf(" "); bam = PTR_ANALYSE(str, tab, 9); // 9 - число слов if (bam!= NULL) printf("\t String found: %s\n press Enter to continue or Ctrl+Z to quit: ", bam->keyword); else printf("\t STRING NOT FOUND:\n press Enter to continue or Ctrl+Z to quit: "); } while ((c = getchar()) != EOF); printf("\n Press any key: "); _getch(); return 0; } В программе ввод слов осуществляется посимвольно с помощью специальной функции getLine(). Шаблон структуры – это struct key, для которого определяется переменная tab[] как массив структур. Одновременно определяется указатель на эту структуру, т. е. *bam. Функция struct key *PTR_ANALYSE() возвращает указатель на структуру. Параметрами этой функции являются указатель на тип char, массив структур и целая переменная. Цикл do – while применен для того, чтобы тело цикла выполнялось хотя бы один раз. Массив структур с именем tab[] инициализирован служебными словами – операторами управления. Результат выполнения функции struct key *PTR_ANALYSE() присваивается указателю *bam, который определен по шаблону глобальной структуры. Возможный результат выполнения программы показан на рис. 15.5.
Задание 5 В программе используйте прототипы вспомогательных функций. В качестве служебных слов задайте основные типы данных и их производных языка программирования С. Дополните программу подсчетом количества символов служебных слов с выводом на консоль как самого слова, так и его длины. Функцию анализа вводимых слов с клавиатуры напишите без дополнительного указателя на структуру типа key. В программе предусмотрите копирование консольного содержания в текстовый файл с именем compX. txt, где Х – номер компьютера, за которым выполняется лабораторная работа.Пример 6. Напишите программу сравнения введенного целого числа с имеющимися целыми случайными числами одномерного массива на основе структурного типа данных и двоичного поиска (метода половинного деления) для упорядоченного массива. Программный код решения примера: #include <stdio. h> #include <conio. h> #include <stdlib. h> #include <time. h> #include <locale. h> #define MAX 1000 // Функция ввода числа как строки с клавиатуры void getLine(int num[], int m) { int i, j, k; time_t tic; srand((unsigned) time(&tic)); for (i = 0; i < m; ++i) num[i] = (int)m*rand()/RAND_MAX; printf("\n\t The initial array of numeric data:\n"); for (i = 0; i < m; ++i) printf(" %3d", num[i]); puts(" "); // Сортировка массива по возрастанию for (i = 1; i < m; ++i) { for (j = 0; j < m-1; ++j) { if (num[j] > num[j+1]) { k = num[j]; num[j] = num[j+1]; num[j+1] = k; } } } printf("\t Assorted array of numerical data:\n"); printf(" "); for (i = 0; i < m; ++i) printf(" %3d", num[i]); puts(" "); } // Создание структуры глобального типа struct numb { int index; int numbers; } tab, *bam;// указатель на структуру // Вспомогательная функция // с указателем на структуру numb struct numb *PTR_ANALYSE(int x, int mass[], int n) { int min = 0; int max = n - 1; int mid; struct numb *PTR = &tab; // Бинарный поиск while (min <= max) { mid = (max + min)/2; // переход в середину массива if (x == mass[mid]) { PTR->numbers = mass[mid]; PTR->index = mid; return (PTR); // возвращение указателя на структуру } else if (x < mass[mid]) max = mid - 1; else min = mid + 1; } // End while return NULL; // число не найдено } // End function // Главная функция int main (void) { int c, x; int N, arr[MAX]; // Русские шрифты setlocale(LC_ALL, ".1251"); printf("\n УГАДЫВАНИЕ ЧИСЛА В ЧИСЛОВОМ ОТСОРТИРОВАННОМ МАССИВЕ\n"); // Английские шрифты setlocale(LC_ALL, "English"); printf("\n The end of the session: press Ctrl+Z after pressing Enter\n"); printf("\n\t Enter the dimension of the array of more than 3: "); scanf_s("%d", &N); _flushall(); getLine(arr, N); puts(" "); do { printf("\t Enter an integer: "); scanf_s("%d", &x); _flushall(); printf(" "); bam = PTR_ANALYSE(x, arr, N); if (bam!= NULL) { printf("\t The number is found: %d\n ", bam->numbers); printf("\t The index number of assorted array: %d\n", bam->index+1); printf("\n\t Press Enter to continue or Ctrl+Z to quit: "); } else printf("\t The number is not found.\n Press Enter to continue or Ctrl+Z to quit: "); } while ((c = getchar()) != EOF); printf("\n Press any key: "); _getch(); return 0; } При двоичном алгоритме поиска после каждого сравнения исключается половина элементов массива, в котором производится поиск 15.5. Алгоритм находит средний элемент массива и сравнивает его с ключом поиска (в программе переменная х). Если они равны, ключ поиска считается найденным и возвращается индекс этого элемента. Если они не равны, задача упрощается до поиска в одной половине массива. Если ключ поиска меньше среднего элемента массива, поиск производится в первой половине массива. В противном случае поиск производится во второй половине. Если ключ поиска в указанном подмассиве не найден, алгоритм повторяется для четверти массива. Поиск продолжается до тех пор, пока ключ не окажется равен среднему элементу подмассива или пока подмассив не будет состоять из одного элемента, не равного ключу (это означает, что ключ поиска не найден). Возможный результат выполнения программы показан на рис. 15.6.
Задание 6 Перед началом бинарного поиска по заданному ключу отсортируйте массив случайных чисел по убыванию. Сформируйте случайный массив из 5*Х целых чисел. Интервал формирования случайных чисел примите от Х до 5*Х, где Х – номер компьютера, за которым выполняется лабораторная работа. Выведите отсортированный массив на консоль. Введите число (ключ поиска), соответствующее последнему элементу массива. Подсчитайте количество итераций (количество сравнений в двоичном поиске), которое потребуется для поиска заданного ключа. Заданную программу дополните поиском по заданному ключу последовательным перебором. Определите количество сравнений, которое потребуется при бинарном поиске и с помощью последовательного перебора.Контрольные вопросы Какие операции над структурами разрешены в языке С? Как осуществляется передача частей структуры в качестве аргументов функции? Как осуществляется возврат частей структуры из функции? Как реализуется возвращение измененной структуры из функции? Как осуществляется обращение к полям структуры, переданной функции в виде аргумента? Допустимо ли объявление переменных глобальной структуры и переменных структуры функции одними и теми же идентификаторами? Как распределить структуру в динамической памяти? Как осуществляется возврат данных структурного типа из пользовательской функции? | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Программирование на языке C в Microsoft Visual Studio 2010 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
16. Лекция: Операции с разрядами (битами) в языке С: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Теоретическая часть 16.1. Базовые системы счисления В языке программирования С предусмотрена возможность по управлению отдельными разрядами (битами) значения переменной. Эта возможность связывается с различным представлением числа в компьютере, а именно с различными основаниями системы счисления: двоичной системой счисления, восьмеричной системой счисления, шестнадцатеричной системой счисления. У бита возможны только два значения: 0 и 1. Техническая реализация таких состояний легко реализуется, например, включено–выключено, положительное значение напряжения–отрицательное значение напряжения (определенного уровня) и т. д. В языке программирования С термин байт используется для обозначения размера (разрядности) хранения набора символов. Поэтому в языке С байт может содержать 8, 9, 16 и другое количество разрядов. Однако в характеристиках модулей памяти и систем передачи данных предполагается, что байт содержит восемь разрядов[16.1]. Разряды байта пронумерованы справа налево числами от 0 до 7. Седьмой разряд (крайний левый) называется старшим, а нулевой разряд (крайний правый) – младшим. Байт имеет наибольшее значение, когда все его разряды установлены, т. е. имеют значение 1. Например, для 8 разрядов с учетом двоичной системы счисления в случае возможного наибольшего значения получим:
Наименьшему значению соответствует комбинация нулей , которая представляет собой просто нуль [9]. Байт может хранить числа от 0 до 255, что составляет 256 возможных значений. Программа может интерпретировать комбинацию разрядов иначе и применять байт для хранения чисел от –128 до 127, что составляет 256 возможных значений. Например, тип unsigned char обычно характеризуется использованием байта для представления чисел в диапазоне от 0 до 255, тип signed char – до 127. В основании восьмеричной системы счисления лежит число 8 (23). Каждое знакоместо восьмеричного числа соответствует определенной степени восьми. Для записи используются цифры от 0 до 7. Каждая цифра восьмеричного числа соответствует трем двоичным цифрам. Двоичные эквиваленты восьмеричных цифр представлены в табл. 16.1.
В шестнадцатеричной системе счисления используются степени числа 16 и цифры от 0 до 15. Для представления цифр, соответствующих десятичным значениям от 10 до 15, используются буквы от A до F [16.1]. Например, шестнадцатеричное число A3F (в языке С записывается как 0xA3F) представляет значение
В приведенной записи числу 10 соответствует А, а числу 15 – F. Язык С допускает использование букв нижнего и верхнего регистра (строчные и прописные буквы) для обозначения шестнадцатеричных цифр. Таким образом, число 2 623 в шестнадцатеричной системе счисления можно записать как 0xA3F, так и 0xa3f. Каждая цифра шестнадцатеричного числа соответствует 4-значному двоичному числу [16.1]. Поэтому две шестнадцатеричных цифры соответствуют одному восьмиразрядному байту. Первая цифра представляет 4 старших разряда, а вторая цифра – 4 младших разряда. Соответствие между шестнадцатеричными цифрами, десятичными и двоичными числами показано в табл. 16.2.
В языке С существуют два средства управления разрядами [16.1]. Первое представляет собой набор поразрядных операций, а второе – форму полей данных, которое предоставляет доступ к разрядам значения типа int. Далее будут использоваться 8-разрядные числа в двоичной системе счисления. 16.2. Логические поразрядные операции В языке программирования С существуют два вида поразрядных операций: логические операции и операции сдвига [16.1]. Поразрядные операции выполняются над каждым разрядом независимо от разрядов слева или справа. Поразрядные операции выполняются над целыми числами. Рассмотрим логические поразрядные операции. Унарная операция ∼ преобразовывает все единицы в нули и все нули в единицы (предполагается, что операции производятся над двоичными числами). Данную операцию называют также операцией "дополнение", т. е. когда все биты, равные 0, устанавливаются в 1, а когда все биты, равные 1, – устанавливаются в 0. Поразрядная операция И обозначается символом &. Двоичная операция & создает новое значение за счет выполнения поразрядного сравнения двух операндов. Для каждой позиции результирующий разряд будет иметь значение 1 только в случае, когда соответствующие разряды обоих операндов имеют значение 1. Можно сказать, что когда над двумя значениями производится операция поразрядного умножения &, то двоичные представления чисел сравниваются бит за битом [2]. Например, пусть одна переменная w1 есть число 25, а другая w2 – число 77. Соответственно в двоичном представлении 25 = , 77 = . Тогда в результате поразрядной операции & получим число w3 = w1 & w2. Результат действия оператора & можно представить следующим образом:
В результате поразрядной операции "&" над двумя числами (25 и 77 в двоичном представлении), которые имеют, например, 16 бит, получили новое число, т. е. 9. Поразрядный оператор "&" часто используется для операций маскирования. С его помощью принудительно можно установить заданный бит данных в значение 0. Например, в утверждении w3 = w1 & 3; переменной w3 присваивается значение переменной w1, для которой выполнена поразрядная операция & со значением 3. При этом все биты левее двух младших битов устанавливаются в 0, а остальные биты сохраняют свое значение [16.2]. Маска представляет собой некоторую комбинацию разрядов [16.1]. Разряды маски с нулевыми значениями можно считать аналогом непрозрачных ячеек реальной маски, а разряды со значениями 1 – прозрачными ячейками. Поразрядная операция & называется также конъюнкцией, или логическим умножением. Применяется часто обозначение AND. Поразрядная операция ИЛИ обозначается символом |. Когда над двумя значениями производится операция поразрядно ИЛИ, то последовательно сравниваются значения всех битов при двоичном представлении этих значений [5]. Если при этом соответствующий бит имеет значение 1 в первом или втором операнде, то результирующее значение будет равно 1. Рассмотрим предыдущий пример с поразрядной операции ИЛИ:
Поразрядную операцию ИЛИ (|) обычно используют для установки заданных битов слова в 1. Логическую операцию ИЛИ называют также включающей дизъюнкцией, или логическим сложением. Часто применяется обозначение OR. Поразрядная операция исключающего ИЛИ (^) работает следующим образом [5]. Сравниваются соответствующие биты двух операндов, и если только один из битов равен 1, то результат будет равен 1. А при равенстве обоих соответствующих битов или 0, или 1 результат будет равен 0. Для двух операндов b1, b2 при использовании исключающего ИЛИ (^) справедлива таблица истинности (табл. 16.3).
Если операцию исключающего ИЛИ (^) использовать для одного и того же значения, то в результате будет получено нулевое значение. Этот прием часто использовался программистами на языке ассемблера как наиболее быстрый путь установить значение в нуль или сравнить два значения на их равенство. Этот способ не рекомендуется использовать в языке программирования С, так как при этом скорость работы не повышается, а программа становится менее понятной [16.2]. Операция исключающего ИЛИ (^) может применяться для перестановки значений двух переменных без выделения дополнительной памяти (и, соответственно, без использования дополнительной переменной). Поразрядная операция исключающего "ИЛИ" называется также исключающей дизъюнкцией. Часто применяется обозначение XOR. 1.3. Поразрядные операции сдвига Оператор сдвига влево: << Когда оператор сдвига влево (<<) выполняется над некоторым значением, все биты, составляющие это значение, сдвигаются влево [16.2]. Связанное с этим оператором число показывает количество бит, на которое значение должно переместиться. Биты, которые сдвигаются со старшего разряда, считаются потерянными, а на место младших битов всегда помещаются нули. Оператор сдвига вправо: >> Операция сдвига вправо (>>) сдвигает разряды левого операнда вправо на количество позиций, указываемое правым операндом. Выходящие за правую границу разряды теряются. Для типов данных без знака (unsigned) освобождаемые слева позиции заполняются нулями. Для знаковых типов данных результат зависит от используемой системы. Освобождаемые позиции могут заполняться нулями либо копиями знакового (первого слева) разряда [16.1]. Поразрядные операции сдвига могут служить удобным и эффективным (в зависимости) средством выполнения операций умножения и деления на числа, представляющие собой степени двойки [16.1]. Такие операции аналогичны смещению десятичной точки при умножении или делении на 10. 1.4. Битовые поля Второй метод управления разрядами состоит в использовании битового (разрядного) поля [9], которое представляет собой просто последовательную цепочку разрядов в рамках значения типа signed int или unsigned int. Битовое поле может быть только элементом структуры или объединения и вне объектов этих типов не встречается [16.3]. Битовое поле создается путем объявления структуры (объединения), которая помечает каждое поле и определяет его разряд. Приведем пример из [16.2] с использованием битовых полей в структуре: struct packed_struct { unsigned int : 3; unsigned int f1 : 1; unsigned int f2 : 1; unsigned int f3 : 1; unsigned int type : 8; unsigned int index : 18; }; В созданном шаблоне-структуре с дескриптором (именем-этикеткой) packed_struct первый член не имеет имени. Символ,":3" задает три безымянных бита. Второй, третий и четвертый члены структуры, f1, f2, f3, также имеют тип unsigned int. Символ ":1" говорит о том, что в данном члене структуры будет храниться 1 бит. Член структуры с именем type в памяти занимает 8 бит. Член структуры index рассчитан на хранение 18 бит. Для заданного шаблона структуры можно определить структурную переменную, например: struct packed_struct packed_data; После этого можно присваивать значения полям структуры, например: packed_data. type = 7; Если ранее была объявлена какая-то переменная, например, n, то присвоение может быть таким: packed_data. type = n; При этом нет необходимости беспокоиться о том, что значение переменной n будет слишком большим. Только младшие 8 бит будут учитываться при присваивании значения для поля packed_data. type. Для извлечения битовых полей структуры можно использовать обычное утверждение: n = packed_data. type; В приведенном примере после извлечения значения поля type будет произведен сдвиг в сторону младших бит. Битовые поля могут быть объявлены только как тип int (в стандарте С99 также _Bool). Если битовое поле имеет тип int, то этот знаковый (signed) или беззнаковый (unsigned) тип зависит от реализации. Для исключения неоднозначности следует использовать явные объявления: signed int или unsigned int. Битовые поля нельзя объединять в массивы. Нельзя использовать адрес битового поля, поэтому не может быть такого типа, как "указатель на битовое поле" [16.2]. Компилятор языка программирования С не переупорядочивает битовые поля для получения оптимального распределения памяти. Но в некоторых случаях может производиться выравнивание за счет безымянного поля. Это может использоваться для выравнивания следующего поля структуры по границе блока [16.2]. С помощью битовых полей можно формировать объекты с длиной внутреннего представления, не кратной байту. Практическая часть Пример 1. Напишите программу по демонстрации операции поразрядного отрицания (поразрядного дополнения) числа без знака, вводимого с клавиатуры, с использованием операций побитового сдвига. Для решения примера будем задавать какое-то число, которое представим в виде нескольких разрядов, после чего через операции побитового сдвига изменим его и выведем на консоль. Программный код решения примера: #include <stdio. h> #include <conio. h> // Прототип функции void printBits(unsigned int var); // Главная функция int main (void) { unsigned int number; printf("\n The program on demonstration digit-by-digit operation of denying ( ~ )\n"); printf("\n\t Enter a whole number of unsigned: "); scanf_s("%d", &number); printf("\n\t Binary representation of the starting number and\n"); printf("\t Binary representation of bitwise negation of the initial number:\n"); printBits(number); //Исходное число printBits(~number); // Число после поразрядного дополнения printf("\n\n Press any key: "); _getch(); return 0; } // Функция побитового представления целого числа без знака void printBits(unsigned int var) { unsigned int b; unsigned int mask = 1 << 31; // shift to 31 bit printf("\n\t %10u = ", var); for (b = 1; b <= 32; ++b) { printf("%c", var & mask? '1' : '0'); var <<= 1; // or: var = var << 1; if (b % 8 == 0) putchar(' '); } } В программе применен форматный ввод числа в виде %u и вывод числа в виде %10u, где u применяется для беззнакового типа числа, 10 – это количество позиций, отводимое для десятичного числа. Предполагается, что заданное число может быть представлено 32 разрядами, по 4 группы с 8 разрядами (4 байта по 8 бит в каждом). Применение оператора сдвига (<<) позволяет все биты данного числа сдвигать на единицу (действие в цикле for) влево. При этом используется операция поразрядного И (&). С помощью оператора условия (?) выполняется замена 1 на 0 и наоборот. Возможный результат выполнения программы показан на рис. 16.1.
Как видно из рис. 16.1, все нули или единицы исходного числа (в двоичном представлении) были инвертированы. Задание 1 Проверьте результат выполнения программы с помощью инженерного калькулятора calc операционной системы Windows. Выполните раздельное объявление переменной mask и раздельное ее определение (инициализацию с поразрядным сдвигом). В теле цикла for вместо функции printf() примените putchar(). Дополните программу выводом результатов в файл compX. txt, где Х – номер компьютера, за которым выполняется лабораторная работа. В качестве стартового числа примите год рождения пользователя.Пример 2. Напишите программу выполнения поразрядной операции И (&) над двумя целыми числами, представленными в двоичной системе счисления для 32-разрядного компьютера. Одно из чисел будем считать маской, относительно которой "просеваются" единицы двоичного числа. Программный код решения примера: #include <stdio. h> #include <conio. h> // Прототип функции void printBits(unsigned int var); // Главная функция int main (void) { unsigned int number, mask; printf("\n The program on demonstration digit-by-digit operation And ( & )\n"); printf("\n\t Enter a whole number of unsigned: "); scanf_s("%u", &number); printf("\t Enter the number of unsigned-mask: "); scanf_s("%u", &mask); printf("\n\t Binary representation of the starting number and (%u) and\n", number); printf("\t Binary representation of the number-masks (%u):\n", mask); printBits(number); printBits(mask); // Число после поразрядного умножения (И) printBits(number & mask); printf("\n\n Press any key: "); _getch(); return 0; } // Функция побитового представления целого числа без знака void printBits(unsigned int var) { unsigned int b; unsigned int mask = 1 << 31; // shift to 31 bit printf("\n\t %10u = ", var); for (b = 1; b <= 32; ++b) { printf("%c", var & mask? '1' : '0'); var <<= 1; // or: var = var << 1; if (b % 8 == 0) putchar(' '); } // End 2nd for } // End function В программе операция поразрядного умножения – операция И закладывается в фактический параметр функции printBits(). Возможный результат выполнения программы показан на рис. 16.2.
Как видно из рис. 16.2, битовая единица первого числа "проходит" в результат, если в маске на этом же месте (в том же разряде) также находится битовая единица. В результате получается новое число – 32. Задание 2 В качестве стартового числа примите год рождения пользователя, а в качестве маски – число рождения. Примените операцию поразрядного умножения к введенным отрицательным числам и выведите результат на консоль. Сравните с тем, что выводит функция printBits(). Вместо оператора условия (?) примените другой способ вывода 1 или 0 после применения поразрядной операции И. Консольный вывод запишите в текстовый файл с именем compX. txt, где Х – номер компьютера, за которым выполняется лабораторная работа.Пример 3. Напишите программу демонстрацию поразрядной операции "ИЛИ" (включающей дизъюнкции). Операция поразрядного логического ИЛИ (|) сравнивает каждый бит первого операнда с соответствующим битом второго операнда. Если любой (или оба) из сравниваемых битов равен 1, то соответствующий бит результата устанавливается в 1, в противном случае результирующий бит равен 0. #include <stdio. h> #include <conio. h> // Прототип функции void printBits(unsigned int var); // Главная функция int main (void) { unsigned int number1, number2; printf("\n The program on demonstration digit-by-digit operator OR ( | ):\n"); printf("\n\t Enter the unsigned number of N1: "); scanf_s("%u", &number1); printf("\t Enter the unsigned number of N2: "); scanf_s("%u", &number2); printf("\n\t Binary representation of the number of N1 (%u) and\n", number1); printf("\t Binary representation of the number of N2 (%u):\n", number2); printBits(number1); printBits(number2); // Число после поразрядного сложения (ИЛИ) printBits(number1 | number2); printf("\n\n Press any key: "); _getch(); return 0; } // Функция побитового представления целого числа без знака void printBits(unsigned int var) { unsigned int b; unsigned int num = 1 << 31; // shift to 31 bit printf("\n\t %10u = ", var); for (b = 1; b <= 32; ++b) { printf("%c", var & num? '1' : '0'); var <<= 1; // or: var = var << 1; if (b % 8 == 0) putchar(' '); } // End 2nd for } // End function В программе использована функция printBits(), как и в предыдущем примере. Она по необходимости преобразует десятичные числа в двоичные. При этом входным параметром функции является поразрядное включающее "ИЛИ" над двумя операндами в главной функции main(). Возможный результат выполнения программы показан на рис. 16.3.
Как видно из полученного результата, поразрядная операция ИЛИ представляет собой побитовое сложение чисел при условии, что 1 + 1 = 1. Задание 3 В качестве исходных чисел примите год и число рождения пользователя. Произведите операцию включающего ИЛИ над двумя десятичными числами в соответствии с предыдущим пунктом задания. Операцию выполните в главной функции main(). Видоизмените программу для случая, когда имеется 16-разрядный компьютер.Пример 4. Напишите программу демонстрацию поразрядного исключающего ИЛИ (^). Операция поразрядного исключающего ИЛИ (^) сравнивает каждый бит первого операнда (например, первого числа) с соответствующими битами второго операнда. Если один из сравниваемых битов равен 0, а второй бит равен 1, то соответствующий бит результата устанавливается в 1, в противном случае, т. е. когда оба бита равны 1 или 0, бит результата устанавливается в 0. Программный код решения примера: #include <stdio. h> #include <conio. h> // Прототип функции void printBits(unsigned int var); int main (void) { unsigned int number1, number2; printf("\n The program on demonstration digit-by-digit excluding operator OR ( ^ ):\n"); printf("\n\t Enter the unsigned number of N1: "); scanf_s("%u", &number1); printf("\t Enter the unsigned number of N2: "); scanf_s("%u", &number2); printf("\n\t Binary representation of the number of N1 (%u) and\n", number1); printf("\t Binary representation of the number of N2 (%u):\n", number2); printBits(number1); printBits(number2); printBits(number1 ^ number2); // Число после операции ^ printf("\n\n Press any key: "); _getch(); return 0; } // Функция побитового представления целого числа без знака void printBits(unsigned int var) { unsigned int b; unsigned int num = 1 << 31; // shift to 31 bit printf("\n\t %10u = ", var); for (b = 1; b <= 32; ++b) { printf("%c", var & num? '1' : '0'); var <<= 1; // or: var = var << 1; if (b % 8 == 0) putchar(' '); } // End 2nd for } // End function Возможный результат выполнения программы показан на рис. 16.4.
Задание 4 В качестве исходных десятичных чисел примите год и число рождения пользователя. Произведите операцию исключающего ИЛИ над двумя десятичными числами в соответствии с предыдущим пунктом задания. Операцию выполните в главной функции main(). Подсчитайте число итераций оператора цикла for.Пример 5. Напишите программу выполнения поразрядных операций с шестнадцатеричными числами, которые получаются после преобразования десятичных чисел, введенных с клавиатуры. Программный код решения примера: #include <stdio. h> #include <conio. h> const char basestr[16] = {'0','1','2','3','4','5','6','7','8','9', 'A','B','C','D','E','F'}; const char *two16[16] = {"0000","0001","0010","0011","0100","0101","0110","0111", "1000","1001", // 0,1,2,3,4,5,6,7,8,9 "1010","1011","1100","1101","1110","1111"}; //A, B,C, D,E, F // Прототипы функций char *dec2hex(unsigned int var); void printHex2Bin(unsigned int number); // Главная функция int main (void) { int d12; unsigned int number1, number2; char ch, *NUM1; printf("\n The program on demonstration digit-by-digit operations\n with hexadecimal numbers\n"); printf("\n\t Enter the unsigned number of N1: "); scanf_s("%u", &number1); printf("\t Enter the unsigned number of N2: "); scanf_s("%u", &number2); _flushall(); //Для контроля перевода десятичного числа в шестнадцатеричное printf("\n\t Hexadecimal numbers: %X, %X\n", number1, number2); printf("\n\t Enter bit operation ( &, |, ^, ~ ): "); scanf_s("%c", &ch); switch (ch) { case '&': NUM1 = dec2hex(number1 & number2); printHex2Bin(number1 & number2); break; case '|': NUM1 = dec2hex(number1 | number2); printHex2Bin(number1 | number2); break; case '^': NUM1 = dec2hex(number1 ^ number2); printHex2Bin(number1 ^ number2); break; case '~': printf("\t Type 1 - for the first number; type 2 - for the second number: "); scanf_s("%d", &d12); _flushall(); if (d12 == 1) { NUM1 = dec2hex(~number1); printHex2Bin(~number1); } else { NUM1 = dec2hex(~number2); printHex2Bin(~number2); } break; default : fprintf(stdout, "\n\t Unknown symbol."); break; } printf("\n\n Press any key: "); _getch(); return 0; } char *dec2hex(unsigned int var) { int number[64]; int i, j, k = 0; unsigned int base = 16; char Hex[128], *PTR; PTR = Hex; do { number[k] = var % base; ++ k; var /= base; } while (var!= 0); for (i = k-1, j = 0; i >= 0; --i, ++j) { Hex[j] = (char )basestr[number[i]]; PTR[j] = Hex[j]; } PTR[j] = '\0'; return (PTR); } void printHex2Bin(unsigned int number) { unsigned int num = 1 << 31; unsigned int i; printf("\n Hexadecimal number: %s\n", dec2hex(number)); printf(" Binary equivalent:\n "); for (i = 1; i <= 32; ++i) { printf("%2c", (number & num )? '1' : '0'); number <<= 1; if (i % 8 == 0) printf(" "); } } В программе используется следующий указатель на функцию: char *dec2hex(unsigned int var); Это дает возможность возвращать указатель с адресом необходимого символьного массива. Обратите внимание на выполнение операции завершения строки в функции *dec2hex() для указателя *PTR. Возможный результат выполнения программы показан на рис. 16.5.
Задание 5 В программе предусмотрите вывод двоичных эквивалентов для введенных чисел. Измените программу так, чтобы операции с переключателем switch выполнялись в функции printHex2Bin(). В функции сформируйте одномерный массив символов двоичного эквивалента заданного шестнадцатеричного числа. Предусмотрите вывод результата. Выполните программу со всеми поразрядными операциями при вводе чисел: года и дня рождения пользователя. Напишите программу перевода десятичных чисел в восьмеричные (вместо *dec2hex()). Предусмотрите также поразрядные операции с выводом двоичных эквивалентов. В программе предусмотрите копирование консольного содержания в текстовый файл с именем compX. txt, где Х – номер компьютера, за которым выполняется лабораторная работа.Пример 6. Напишите программу ротации битов для заданных чисел, представленных в шестнадцатеричном виде, когда биты с самых крайних разрядов не теряются, а переносятся на противоположную сторону [16.2]. Программный код решения примера: #include <stdio. h> #include <conio. h> #include <string. h> const char basestr[16] = {'0','1','2','3','4','5','6','7','8','9', 'A','B','C','D','E','F'}; int main (void) { int n; unsigned int number, uns; char *NUM1, str[128] = "0x", un[] = "u"; FILE *fid; // Прототипы функций char *dec2hex (unsigned int var); unsigned int rotate (unsigned int value, int n); printf("\n The program for demonstration of bit-by-bit rotation \n of the numbers set in a hexadecimal kind\n"); printf("\n\t Enter the unsigned decimal: "); scanf_s("%u", &number); printf("\t Enter the number of bits of rotation: "); scanf_s("%d", &n); //Для контроля перевода десятичного числа в шестнадцатеричное printf("\n\t Hexadecimal numbers: %X\n", number); NUM1 = dec2hex(number); strcat_s(str, 127, NUM1); strcat_s(str, 127, un); fopen_s(&fid,"temp. txt", "w"); fprintf(fid,"%s", str); fclose(fid); fopen_s(&fid,"temp. txt", "r"); fscanf_s(fid,"%x", &uns); fclose(fid); printf("\n\t After rotating %d-bits: %X\n", n, rotate(uns, n)); printf("\n\n Press any key: "); _getch(); return 0; } //Функция перевода десятичного числа в шестнадцатеричное char *dec2hex(unsigned int var) { int number[64]; int i, j, k = 0; unsigned int base = 16; char Hex[128], *PTR; PTR = Hex; do { number[k] = var % base; ++ k; var /= base; } while (var!= 0); for (i = k-1, j = 0; i >= 0; --i, ++j) { Hex[j] = basestr[number[i]]; PTR[j] = Hex[j]; } PTR[j] = '\0'; return (PTR); } //Функция ротации числа влево или вправо unsigned int rotate (unsigned int var, int n) { unsigned int result, bits; // Ограничение диапазона ротации if (n > 0) n = n % 32; else n = -(-n % 32); if (n == 0) result = var; else if (n > 0) // Ротация влево { bits = var >> (32 - n); result = var << n | bits; } else // Ротация вправо { n = - n; bits = var << (32 - n); result = var >> n | bits; } return (result); } В программе сначала формируется символьное выражение шестнадцатеричного числа с помощью функции dec2hex(). Затем оно приводится к стандартному виду с помощью функции strcat_s(), которая к имеющемуся значению 0х добавляет содержимое строки от функции dec2hex(). После еще добавляется суффикс u типа unsigned int. Для получения собственно шестнадцатеричного числа выполнена операция записи строки в файл и чтения из файла в шестнадцатеричном формате. Далее используется функция rotate() из [16.2]. Ротация переменной на n битов влево производится в соответствии с алгоритмом, состоящим из трех шагов [5]. Сначала извлекаются левые n бит исходного значения в отдельную переменную (bits), которая сдвигается вправо (с помощью поразрядного сдвига >>) на количество позиций (32–n). Затем исходное значение (var) сдвигается влево на n битов и наконец над получившимся значением и из влеченными битами производится операция поразрядной дизъюнкции "ИЛИ" (|). Аналогичные действия выполняются при сдвиге вправо. Возможный результат выполнения программы показан на рис. 16.6.
Задание 6 Дополните программу выводом на консоль прочитанного числа из текстового файла. В качестве вводимых данных используйте год и месяц рождения пользователя (студента). Выполните ротацию шестнадцатеричных чисел, записанных в текстовый файл с именем compX. txt, где Х – номер компьютера, за которым выполняется лабораторная работа. В качестве тестовых чисел и числа битов ротации примите: (0xABCDEF00u, 8), (0xABCDEF00u, –16), (0xFFFF1122u, 4), (0xFFFF1122u, –2), (0xABCDEF00u, 0), (0xABCDEF00u, 44). Напишите программу по ротации восьмеричных чисел.Пример 7. Напишите программу по решению следующего примера. С клавиатуры вводятся два целых числа. Остатки от деления их на 16 заносятся соответственно в 4 младших и 4 старших разряда одного байта. Затем следует напечатать изображение содержимого сформированного байта [16.3]. Программный код решения примера: #include <stdio. h> #include <conio. h> #include <local. h> setlocale(LC_ALL, ".1251"); int main (void) { int m, n; unsigned char k; //Прототипы функций void binar (unsigned char ch); unsigned char code16 (int a, int b); printf("\n\t Введите первое беззнаковое число N1: "); scanf_s("%d", &m); printf("\t Введите второе беззнаковое число N2: "); scanf_s("%d", &n); k = code16(m, n); printf("\n\t Код двух остатков старших и младших разрядов байта: %u", k); binar(k); printf("\n Нажмите любую клавише (Press any key): "); _getch(); return 0; } unsigned char code16 (int a, int b){ //Объединение с вложенной структурой union { unsigned char z; struct { unsigned int x : 4; //Младшие биты unsigned int y : 4; //Старшие биты } hh; } un; un. hh. x = a % 16; un. hh. y = b % 16; return (un. z); } void binar (unsigned char ch){ int i; // Объединение с вложенной структурой union { unsigned char ss; // Структура с битовыми полями struct { unsigned int a0:1; unsigned int a1:1; unsigned int a2:1; unsigned int a3:1; unsigned int a4:1; unsigned int a5:1; unsigned int a6:1; unsigned int a7:1; } byte; } cod; cod. ss = ch; printf("\n\n\t Число разрядов:\n\t"); for (i = 0; i < 8; ++i) printf("%4d", 7 - i); printf("\n\t Значения битовых полей:\n\t"); printf("%4d%4d%4d%4d%4d%4d%4d%4d",\ te. a7, te. a6, te. a5, te. a4,\ te. a3, te. a2, te. a1, te. a0); printf("\n\n"); } Возможный результат выполнения программы показан на рис. 16.7.
В программе используются две переменных типа объединения и две структурные переменные. Объединение имеет то свойство, что переменные разных типов занимают одну область памяти, соответствующей наибольший размер в байтах. Поэтому если инициализируются одни переменные (например, un. hh. x = a % 16; un. hh. y = b % 16;), то переменная другого типа (unsigned char z;) будет располагаться в той же области памяти, что и переменные типа unsigned int (x и y). В связи с этим якобы неинициализированная переменная un. z возвращается функцией code16(). Размеры полей задаются программистом с учетом того, чтобы в них помещалось соответствующее число, представленное в двоичной системе. Например, если размерность поля равна 2, то в это поле можно записать десятичное число 3, так как его двоичный эквивалент равен 11. В поле с размерностью 8 можно записать число 140, так как его двоичный эквивалент равен . Задание 7 В качестве вводимых чисел примите 2*Х, где Х – номер компьютера, за которым выполняется лабораторная работа. Выведите на консоль размерность в байтах объединений и структур, определенных в программе. Проанализируйте результат выполнения программы при изменении размера битовых полей в структуре функции code16(). Видоизмените программу для ввода трех чисел и определения значений разрядных (битовых) полей после занесения в них остатков от деления на целое число 8.Пример 8. Используя битовые поля структуры, напишите программу вывода на экран дисплея двоичного кода ASCII символа, вводимого с клавиатуры. Условие примера является классическим, примеры программ приводятся во многих руководствах и учебниках. Ниже приводится некоторая модификация известных программ. Отметим про наборы символов ASCII. Символы сохраняются в памяти компьютеров с использованием числовых кодов. Часто используется кодировка ASCII (American Standard Code for Information Interchange – американский стандартный код для обмена информацией). Таблицу символов ASCII можно посмотреть в [16.1]. Программный код решения примера: #include <stdio. h> #include <conio. h> //Шаблон структуры с битовыми полями struct byte { int b1 : 1; int b2 : 1; int b3 : 1; int b4 : 1; int b5 : 1; int b6 : 1; int b7 : 1; int b8 : 1; }; // Определение объединения с вложенной структурой union bits { char ch; struct byte bit; } un;// un - переменная типа объединения // Прототип функции void decode (union bits bt, int ch); // Главная функция int main (void) { printf("\n\t Enter any symbol or Ctrl+Z to quit:\n"); do { printf("\n\t Enter: "); un. ch = getchar(); if ( (un. ch) == EOF) break; decode(un, un. ch); } while ((un. ch = getchar())!= EOF); printf("\n Press any key: "); _getch(); return 0; } // Функция двоичного представления символов void decode (union bits bt, int ch) { printf("\tBinary code of '%c':\n", ch); if (bt. bit. b8) printf("%2c 1", ' '); else printf("%2c 0", ' '); if (bt. bit. b7) printf("%2c 1", ' '); else printf("%2c 0", ' '); if (bt. bit. b6) printf("%2c 1", ' '); else printf("%2c 0", ' '); if (bt. bit. b5) printf("%2c 1", ' '); else printf("%2c 0", ' '); if (bt. bit. b4) printf("%2c 1", ' '); else printf("%2c 0", ' '); if (bt. bit. b3) printf("%2c 1", ' '); else printf("%2c 0", ' '); if (bt. bit. b2) printf("%2c 1", ' '); else printf("%2c 0", ' '); if (bt. bit. b1) printf("%2c 1", ' '); else printf("%2c 0", ' '); printf("\n"); } Программа ориентирована на 8 бит одного байта целочисленного значения, которым кодируется символ, вводимый с клавиатуры. В функции decode() использовано форматирование на основе символа "пробел". Возможный результат выполнения программы показан на рис. 16.8.
Задание 8 Вместо цикла do–while примените другой оператор цикла. Напишите программу вывода всех строчных букв латинского алфавита и их двоичных эквивалентов в кодировке ASCII без ввода их с клавиатуры.Пример 9. Напишите программу левого поразрядного сдвига для вводимого с клавиатуры целого числа с выводом его двоичного эквивалента и с повторными сдвигами влево. В программе решения примера следует предусмотреть перевод числа из десятичной системы счисления в двоичную систему счисления. При поразрядном сдвиге влево на освободившееся место (места) двоичного числа записываются нули. Программный код решения примера: #include <stdio. h> #include <conio. h> // Главная функция int main (void) { long int a; unsigned int m, n; //Прототип функции void dec2(long int var, unsigned int m, unsigned int n); printf("\n\t Enter an integer: "); scanf_s("%ld", &a); _flushall(); printf("\t Enter a value shift: "); scanf_s("%u", &m); _flushall(); printf("\t Enter number of repeated shifts: "); scanf_s("%u", &n); _flushall(); dec2(a, m, n); printf("\n\n Press any key: "); _getch(); return 0; } // Функция поразрядного сдвига void dec2(long int var, unsigned int m, unsigned int n) { unsigned int b, i; long int mask = 1 << 31; long int M[128]; for (i = 0; i < n; ++i) M[i] = var << i*m; printf("\n\t Decimal and its binary equivalent after the shift:\n"); for (i = 0; i < n; ++i) { if (i == 0) { printf("\n Initial number %ld:\n\t", M[0]); for (b = 1; b <= 32; ++b) { printf("%c", M[0] & mask? '1' : '0'); M[0] <<= 1; // or: var = var << 1; if (b % 8 == 0) putchar(' '); } } else { printf("\n The following number %ld:\n\t", M[i]); for (b = 1; b <= 32; ++b) { printf("%c", M[i] & mask? '1' : '0'); M[i] <<= 1; if (b % 8 == 0) putchar(' '); } } printf("\n"); } } В программе предполагалось, что в наличии 32-разрядный компьютер. Кроме того, вывод двоичного эквивалента сделан побайтно, считая, что в одном байте находится 8 бит. Возможный результат выполнения программы показан на рис. 16.9.
Задание 9 Проверьте результат выполнения программы с помощью инженерного калькулятора (calc). В программу добавьте нумерацию результатов вывода десятичного числа и его двоичного эквивалента. Введите число своего дня рождения пользователя. Проверьте программу при вводе отрицательных целых чисел. Измените программу для поразрядного сдвига вправо.Контрольные вопросы Как осуществляется нумерация разрядов байта? Для каких систем счисления в языке С имеются классификаторы форматируемых данных? Какие логические поразрядные операции существуют в языке С? Какие логические операции сдвига существуют в языке С? Какими операторами они реализуются? Что такое битовое поле в языке С? Где оно может быть определено? В чем отличие поразрядных и логических операторов НЕ, И и ИЛИ? Как можно обменять значения двух целочисленных переменных без использования третьей переменной? Чем отличается операция сдвига вправо для типов int и unsigned? | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Программирование на языке C в Microsoft Visual Studio 2010 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
17. Лекция: Программы на языке С, состоящие из нескольких файлов: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Теоретическая частьПрограмма на языке С – это совокупность функций. Запуск любой программы начинается с запуска главной функции, содержащей в себе всю остальную часть программы. Внутри главной функции для реализации заданного алгоритма вызываются все другие необходимые функции. Часть функций создается самим программистом, другая часть – библиотечные функции – поставляется пользователю со средой программирования и используется в процессе разработки программ (например, printf(), sqrt() и др.). Простейший метод использования нескольких функций требует их размещения в одном и том же файле. Затем выполняется компиляция этого файла, как если бы он содержал единственную функцию [17.1]. Другие подходы к решению этой проблемы существенно зависят от конкретной операционной системы (Unix-подобные системы, Windows, Macintosh). Компиляторы операционных систем Windows и Macintosh представляют собой компиляторы, ориентированные на проекты [17.1]. Проект описывает ресурсы, используемые программой. Эти ресурсы включают файлы исходного программного кода. Если поместить главную функцию main() в один файл, а определения собственной функции программиста – во второй файл, то первому файлу нужны прототипы функций. Для этого можно хранить прототипы функций в одном из заголовочных файлов. Хороший тон в программировании рекомендует размещать прототипы функций и объявлять их константы в заголовочном файле [17.1]. Назначение отдельных задач отдельным функциям способствует улучшениям программы. Функция может быть либо внешней (по умолчанию), либо статической. К внешней функции доступ могут осуществлять функции из других файлов, в то же время статическая функция может использоваться только в файле, в котором она определена [17.1]. Например, возможны следующие объявления функций: double gamma(); // внешняя функция по умолчанию static double beta(); extern double delta(); Функция gamma() и delta() могут использоваться функциями из других файлов, которые являются частью программы, тогда как beta() – нет. В силу этого применение функции beta() ограничено одним файлом, поэтому в других файлах можно использовать функции с тем же именем. Одна из причин использования класса статической памяти заключается в необходимости создания функций, приватных для конкретных модулей, благодаря чему во многих случаях удается избежать конфликта имен [17.1]. Обычная практика состоит в том, что при объявлении функции, определенной в другом файле, указывается ключевое слово extern. При этом просто достигается большая ясность, поскольку при объявлении функция и предполагается как extern, если только не задано ключевое слово static. Одним из золотых правил для надежного программирования есть принцип "необходимости знать", или принцип минимально необходимой области видимости [17.1]. Рекомендуется держать всю внутреннюю работу каждой функции максимально закрытой по отношению к другим функциям, используя совместно только те переменные, без которых нельзя обойтись по логике программы. Другие классы памяти полезны, и ими можно воспользоваться. Однако всякий раз следует задать вопрос: а есть ли в этом необходимость? Память, использованная для хранения данных, которыми манипулирует программа, может быть охарактеризована продолжительностью хранения, областью видимости и связыванием [17.1]. Продолжительность хранения может быть статической, автоматической или распределенной. Если продолжительность хранения статическая, память распределяется в начале выполнения программы и остается занятой на протяжении всего выполнения. Если продолжительность хранения автоматическая, то память под переменную выделяется в момент, когда выполнение программы входит в блок, в котором эта переменная определена, и освобождается, когда выполнение программы покидает этот блок. Если память выделяется, то она выделяется с помощью функции malloc() (или родственной функции) и освобождается посредством функции free(). Область видимости определяет, какая часть программы может получить доступ к данным. Переменные, определенные вне пределов функции, имеют обла сть видимости в пределах фа йла и видимы в любой функции, определенной после объявления этой переменной. Переменная, определенная в блоке или как параметр функции, видима только в этом блоке и в любом из блоков, вложенных в этот блок. Связывание описывает экстент (протяжение, пространство), в пределах которого переменная, определенная в одной части программы, может быть привязана к любой другой части программы. Переменная с областью видимости в пределах блока, будучи локальной, не имеет связывания. Переменная с областью видимости в пределах файла имеет внутреннее или внешнее связывание. Внутреннее связывание означает, что переменная может быть использована в файле, содержащем ее определение. Внешнее связывание означает, что переменная может быть использована в других файлах. Стандарт языка С поддерживает 4 спецификатора класса памяти [17.2]:
Спецификаторы сообщают компилятору, как он должен разместить соответствующие переменные в памяти. Спецификатор класса памяти в объявлении всегда должен стоять первым [2]. Приведем характеристику спецификаторов классов памяти [17.2]. Спецификатор externВ языке С при редактировании связей к переменной может применяться одно из трех связываний: внутреннее, внешнее или же не относящееся ни к одному из этих типов. В общем случае к именам функций и глобальных переменных применяется внешнее связывание. Это означает, что после компоновки они будут доступны во всех файлах, составляющих программу. К объектам, объявленным со спецификатором static и видимым на уровне файла, применяется внутренне связывание, после компоновки они будут доступны только внутри файла, в котором они объявлены. К локальным переменным связывание не применяется и поэтому они доступны только внутри своих блоков. Спецификатор extern указывает на то, что к объекту применяется внешнее связывание, именно поэтому они будут доступны во всей программе. Объявление (декларация) объявляет имя и тип объекта. Описание (определение, дефиниция) выделяет для объекта участок памяти, где он будет находиться. Один и тот же объект может быть объявлен неоднократно в разных местах, но описан он может быть только один раз. Пример использования спецификатора extern при использовании глобальных переменных: #include <stdio. h> #include <conio. h> // Главная функция int main (void) { // объявление глобальных переменных extern int a, b; printf("\n\t a = %d; b = %d\n", a, b); printf("\n Press any key: "); _getch(); return 0; } // инициализация (описание) глобальных переменных int a = 33, b = 34; Описание глобальных переменных дано за пределами главной функции main(). Если бы их объявление и инициализация встретились перед main(), то в объявлении со спецификатором extern не было бы необходимости. При компиляции выполняются следующие правила: Если компилятор находит переменную, не объявленную внутри блока, он ищет ее объявление во внешних блоках. Если не находит ее там, то ищет среди объявлений среди объявлений глобальных переменных.Спецификатор extern играет большую роль в программах, состоящих из многих файлов [17.3]. В языке С программа может быть записана в нескольких файлах, которые компилируются раздельно, а затем компонуются в одно целое. В этом случае необходимо как-то сообщить всем файлам о глобальных переменных программы. Самый лучший (и наиболее переносимый) способ сделать это – определить (описать) все глобальные переменные в одном файле и объявить их со спецификатором extern в остальных файлах, например, как это сделано в следующей программе:
В программе первый файл – это основная часть программного проекта. Второй файл создан как текстовый файл (с помощью блокнота) с расширением *.h. Список глобальных переменных (x, y, ch) копируется из первого файла во второй, а затем добавляется спецификатор extern. Он сообщает компилятору, что имена и типы переменных, следующих далее, объявлены в другом месте. Все ссылки на внешние переменные распознаются в процессе редактирования связей. Подключение второго файла выполнено с указанием имени диска (D:), на котором расположен файл second. h. Для подключения имени файла, созданного пользователем, его заключают в двойные кавычки. Результат выполнения программы показан на рис. 17.1
В общем случае h-файл (например, second. h) формируется редактором кода: надо создать заготовку обычным способом, очистить все поле редактора и записать в это поле необходимые данные (программный код созданной функции). Затем выполнить команду главного меню: File/Save as и выбрать для сохраняемого файла расширение. h в раскрывающемся списке типов сохраняемого файла: C++ Header Files (*.h; *.hh; *.hpp; *.hxx; *.inl; *.tlh; *.tli). Сохраненный файл с расширением .h следует подключить к проекту. Для этого потребуется в узле Solution Explorer навести курсор мыши к папке Header Files и правой кнопкой мыши выбрать Add – Existing Item сохраненный файл second. h. Затем с помощью оператора #include файл следует включить в основную программу. Другой способ, реализуемый в Microsoft Visual Studio 2010, состоит в том, что сразу через пункт меню "File" выбрать "New"
Далее в правом нижнем углу нажмем клавишу Open. Откроется пустое поле – заготовка для набора необходимого кода. По умолчанию этот файл имеет имя Header1.h. При повторном создании заголовочного файла это будет Header2.h и т. д. После написания кода можно сохранить этот заголовочный файл по желанию в любом каталоге с любым (допустимым) именем (а расширение остается .h). Спецификатор staticПеременные, объявленные со спецификатором static, хранятся постоянно внутри своей функции или файла. В отличие от глобальных переменных они невидимы за пределами своей функции или файла, но они сохраняют значение между вызовами [17.2]. Спецификатор static воздействует на локальные и глобальные переменные по-разному. Коренное отличие статических локальных от глобальных переменных заключается в том, что статические локальные переменные видны только внутри блока, в котором они объявлены. Если бы не было статических переменных, вместо них пришлось бы использовать глобальные, подвергая их риску непреднамеренного изменения другими участками программы. При инициализации статической локальной переменной следует учитывать, что значение присваивается ей только один раз – в начале работы всей программы, но не при каждом входе в блок программы, как обычной локальной переменной. Спецификатор static в объявлении глобальной переменной заставляет компилятор создать глобальную переменную, видимую только в том файле, в котором она объявлена. В таком случае статическая глобальная переменная подвергается внутреннему связыванию. Таким образом, имена локальных статических переменных видимы только внутри блока, в котором они объявлены; имена глобальных статических переменных видимы только внутри файла, в котором они объявлены [17.2]. Спецификатор registerСпецификатор register был разработан для того, чтобы компилятор сохранял значение переменной в регистре центрального процессора, а не в памяти, как обычно. Это означает, что операции над регистровыми переменными выполняются намного быстрее [17.2]. Спецификатор register можно применять только к локальным переменным и формальным параметрам функций. В объявлении глобальных переменных применение спецификатора register не допускается. В языке программирования С с помощью оператора & нельзя получить адрес регистровой переменной, потому что она может храниться в регистре процессора, который обычно не имеет адреса. Ощутимый эффект от спецификатора register может быть получен только для переменных целого (int) и символьного (char) типа. Спецификатор autoСпецификатор auto присваивает объявляемым объектам автоматический класс памяти, его можно применять только внутри функции [17.4]. Объявления со спецификатором auto одновременно являются определениями и резервируют память. Автоматический класс памяти определяет собой автоматический период хранения, который могут иметь только переменные. Локальные переменные функции (определенные в списке параметров или в теле функции) обычно имеют автоматический период хранения. Ключевое слово auto определяет переменные с автоматическим хранением явным образом. Автоматическое хранение способствует экономии памяти, поскольку автоматические переменные существуют только тогда, когда они необходимы. Они создаются при запуске функции, в которой они определены, и уничтожаются, когда происходит выход из нее [17.3]. Автоматическое хранение является примером реализации принципа минимальных привилегий [17.3]. Поэтому переменные должны храниться в памяти и быть доступными, когда в данный момент в них нет необходимости. Для переменных со спецификатором auto нет значения по умолчанию. Обычно создаваемые программистом разработки на языке С принято оформлять в виде файлов с расширением. с, хотя расширение может быть любым, например, .txt, .doc и т. д. Соответственно, разработки для С++ имеют расширение .срр. Для создания собственного файла можно использовать инструментарий Microsoft Visual Studio 2010. Для этого следует из пункта меню "File" выбрать подпункт "New" и далее в соответствии с рис. 17.2 выбрать С++File(.cpp). После нажатия клавиши "Open" откроется файл Source1.cpp (при повторном обращении будет Source2.cpp и т. д.). Будет открыто окно редактирования для набора программного кода. После созданный файл можно сохранить с расширением .c. Теперь следует грамотно объявить переменные, используемые в проекте, и функции в файлах типа *.h, *.c. Создаваемые в среде Visual Studio файлы можно раздельно компилировать, т. е. проверять ошибки, которые отслежи ваются при обычной комп иляции. Следует отметить, что создаваемые программистом функции можно создавать обычным блокнотом операционной системы Windows. При этом можно даже оставить расширение .txt. После этого следует предусмотреть в проекте обращение к созданному файлу с данным расширением. При этом ответственность формирования программного кода ложится на программиста, который создает этот файл (файлы). Приведем возможные действия для создания файлов типа *.с и *.h в программной среде MS Visual Studio 2010. Для этого сначала следует открыть стартовую страницу MS Visual Studio 2010. Далее открыть пункт меню File–New –File. Откроется страница, показанная на рис. 17.2. Далее следует выбрать либо С++File(.cpp) либо Header File(.h). Завершить нажатием кнопки Open. Если выбрать С++File(.cpp), то откроется окно для редактирования, которое по умолчанию имеет имя Source1.cpp. Написав необходимый код, надо сохранить файл с именем по усмотрению пользователя, но с расширением. с. Сохранение проведем по цепочке из пункта меню File: File–Save – Source1.cpp As... В пункте меню "Тип файла" выбрать C Source File (*.c). В итоге откроется окно (в котором могут быть ранее созданные проекты), показанное на рис. 17.3.
Сохранить файл можно где угодно. Целесообразно поместить его в папке разрабатываемого проекта, где будет находиться функция main. c. Такие же рекомендации обычно принимаются и для сохранения разрабатываемых программистом h-файлов. В случае, когда проект разрабатывается несколькими программистами, для всех может использоваться один и тот же h-файл. Тогда этот h-файл следует расположить в какой-то директории, расположенной на один или несколько уровней выше, чем папка создаваемого проекта. При этом подключение в программу такого h-файла следует выполнить в соответствии со следующей нотацией: #include "..\..\some. h" Такое подключение означает, что файл some. h находится на два уровня выше, чем главный файл main. c. Можно указывать полный путь расположения заданного h-файла. Если разрабатывается проект, который переносится с диска на диск, то всякий раз придется прописывать полный путь к h-файлу. Поэтому обычно договариваются переносить проект на новый диск со всеми файлами. Тогда каждый программист просто рассчитывает число уровней до заданного h-файла. При этом структура объявлений функций сохранится неизменной, т. е. без указания полного пути. Обычно в h-файлах дается описание прототипов разработанных файлов, постоянных, общих для проекта и препроцессорных директив. В случае использования препроцессорных директив (#define...) следует после них оставить одну пустую строку. Практическая частьПример 1. Рассмотрим пример создания проекта в Microsoft Visual Studio 2010, состоящего из одного заголовочного файла (например, hfile. h) и двух подключаемых функций, созданных программистом (например, degree(), print()). В файле myfile3.c поместим функцию degree(), а функцию print() поместим в файл myfile2.c. Файл с главной функцией создаваемого проекта озаглавим как main. c. При этом файлы hfile. h, myfile3.c и myfile2.c разместим на другом диске, например, на диске D. В качестве примера запрограммируем решение следующей задачи. Сформируйте матрицу, состоящую из Для получения числа +1 или –1 следует использовать возведение в степень числа –1. Для нечетной степени получим –1, а для четной степени – +1. Для этого разработаем специальную функцию возведения целого числа в целую степень. Для вывода результата на консоль или в файл разработаем свою функцию. Программный код решения примера: #include <stdio. h> #include <conio. h> #include <math. h> #include "D:\\hfile. h" int main(void) { double k; printf("\n\tConstruction of a matrix of planning experiment N = 2^k\n"); printf("\n\tEnter a natural number: "); scanf_s("%lf", &amp;amp;amp;k); if (k < 1 || (ceil(k) != k) ) { printf("\n Error! Press any key: "); _getch(); return 0; } // Обращение к функции формирования матрицы планирования print((int)k); printf("\n\n Press any key: "); _getch(); return 0; } // Файл myfile3.c int degree(int x, int k) { int i, a = 1; for (i = 1; i <= k; ++i) a *= x; return a; } // Файл myfie2.c #include <math. h> #include <stdlib. h> void print(int k) { int i, j, N, *PTR; FILE *fid; N = degree(2,k); // N = 2^k // Динамически выделяемая память PTR = (int *)malloc(N*k*sizeof(int)); for (i = 0; i < N; ++i) for (j = 0; j < k; ++j) PTR[i*k + j] = 0; // заполнение нулями for (i = 0; i < N; ++i) for (j = 0; j < k; ++j) PTR[i*k + j] = degree(-1,(int)floor(i/degree(2,j))); printf("\n Matrix is planning experiment, N = 2^k, N = %d, k = %d\n", N, k); if (k < 5) { for (i = 0; i < N; ++i) { printf("\n%4d) ", i+1); for (j = 0; j < k; ++j) printf(" %3d", PTR[i*k+j]); } } else { fopen_s(&amp;amp;amp;fid, "D:\\data. txt", "w"); fprintf(fid, "\r\n Matrix is planning experiment, N = 2^k, N = %d, k = %d\r\n", N, k); for (i = 0; i < N; ++i) { fprintf(fid, "\r\n%4d) ", i+1); for (j = 0; j < k; ++j) fprintf(fid, "%3d", PTR[i*k+j]); } fclose(fid); printf("\n See the result in the file \"D:\\data. txt\" \n"); } } // Заголовочный файл hfile. h #include "D:\myfile3.c" #include "D:\myfile2.c" Подключение файлов сделано в двух местах: в главной функции main() и в заголовочном файле hfile. h. При этом прототипы функций не прописаны. Заголовочный файл hfile. h "выгружает" содержимое файлов myfile3.c и myfile2.c перед главной функцией main(), поэтому в теле функции можно обращаться к функциям degree() и print(). Функция degree() предназначена для возведения целого числа в степень. Она формирует матрицу планирования (с помощью указателя *PTR) и вывода значений матрицы на консоль и (или) в текстовый файл. Имеет важное значение очередность подключения файлов myfile3.c и myfile2.c. Следует обратить внимание на синтаксис заключения в двойные ковычки имени текстового файла data. txt. Функция ceil() возвращает наименьшее целое (представленное в виде значения с плава ющей точкой), которое больше своего аргумента или равно ему [17.2]. Она включена на тот случай, если пользователь введет не целое число Возможный результат выполнения программы при выводе искомой матрицы на консоль показан на рис. 17.4.
Задание 1 Проверьте работу программ при записи результата в файл. В качестве имени текстового файла примите compX. txt, где Х – номер компьютера, на котором выполняется лабораторная работа. В файле hfile. h поменяйте местами подключаемые файлы. Добавьте необходимые изменения, чтобы программа работала корректно (без предупреждений). Вместо разработанной функции degree() примените стандартную функцию pow(). В программу добавьте изменения для вывода матрицы планирования при зеркальном отображении столбцов. В матрицу планирования добавьте в качестве первого столбца массив положительных единиц, т. е. +1.Пример 2. Напишите программу вывода на консоль содержимого текстового файла, расположенного в заданном каталоге. Вывод на консоль содержимого файла оформить в виде отдельного файла. Предположим, что программный проект расположен на диске Е:\Project_C. Текстовый файл расположен на диске D:\data16.txt. Пусть содержимое текстового файла будет следующим: 1, 2, 3, 4, 5 Programming in C department: electronic engineering Программный код решения примера: // Подключаемый файл fun1.c #include <stdlib. h> // Для функции exit() void print_file(char *st) { char str[79+1]; FILE *fid; printf("\n Data from \"%s\":\n", st); if ( (fopen_s(&amp;amp;amp;fid, st, "r")) ) { printf("\n File could not be opened.\n"); printf("\n Break. Press any key: "); _getch(); exit(1); } printf("\n Data from the file \"%s"\n", st); while (!feof(fid)) { fgets(str, 79, fid); printf("%s", str); } fclose(fid); printf("\n"); } // Основной программный модуль проекта main. c #include <stdio. h> #include <conio. h> #include "D:\\fun1.c" // Главная функция int main (void) { char str[79+1]; printf("\n Enter the file name and path: "); gets_s(str, 79); // Вызов функции распечатки файла print_file(str); printf("\n Press any key: "); _getch(); return 0; } Результат выполнения программы показан на рис. 17.5.
Задание 2 На консоль выведите содержимое подключаемого файла fun1.c. Измените расположение подключаемого файла, например, поместите его на диск D:\students\fun1.c. В текстовый файл запишите фамилию и имя пользователя (студента), год рождения, год поступления в университет, наименование специальности. Содержимое текстового файла предыдущего пункта выведите на консоль и запишите в другой текстовый файл с именем compX. txt, где Х – номер компьютера, на котором выполняется лабораторная работа. Запись в текстовый файл оформите в виде функции surname. c, где surname – фамилия пользователя. В программе вместо указателя, являющегося формальным параметром функции print_file(), примените массив символов.Пример 3. Напишите программу преобразования десятичной системы счисления в двоичную, восьмеричную, шестнадцатеричную на основе системы меню. Примените массив указателей на функцию, используйте заголовочные файлы. Перевод заданного десятичного числа в соответствующее число по заданному основанию оформим в виде трех функций, каждая из которых будет вызываться по индексу, по которому из массива будет выбираться указатель на нужную функцию. Прототипы функций включим в заголовочный h-файл. Основания систем счисления также поместим в заголовочный h-файл. Для решения примера используем программы предыдущего примера. Программный код решения примера состоит из шести файлов: // Основной программный модуль main. c #include <stdio. h> #include <conio. h> #include "hdec. h" int main (void) { int index[3] = {0, 1, 2}; long int number; int basen; void (*fun[3])(long int) = {dec2, dec8, dec16}; printf("\n Enter a positive integer numbers: "); scanf_s("%ld", &amp;amp;amp;number); printf("\n Select base system notation (2, 8, 16): "); scanf_s("%d", &amp;amp;amp;basen); if (basen == 2) (*fun[0])(number); else if (basen == 8) (*fun[1])(number); else if (basen == 16) (*fun[2])(number); else { printf("\n\t Error choice."); printf("\n Break. Press any key: "); _getch(); return 0; } printf("\n\n Press any key: "); _getch(); return 0; } // Подключаемый файл Dec_2.c // Перевод в двоичную систему счисления #include "base. h" void dec2 (long int x) { int i = 0; int Num[64]; int next; long int xx = x; do { Num[i] = x % base2; ++i; x /= base2; } while (x!= 0); // Результат в обратном порядке printf("\n Decimal number \"%ld\" has a binary equivalent:\n", xx); for (--i; i >= 0; --i) { next = Num[i]; printf("%2d", next); } } // Подключаемый файл Dec_8.c // Перевод в восьмеричную систему счисления #include "base. h" void dec8 (long int y) { int i = 0, next; int Num[64]; long int yy = y; do { Num[i] = y % base8; ++i; y /= base8; } while (y!= 0); // Результат в обратном порядке printf("\n Decimal number \"%ld\" has an octal equivalent:\n", yy); for (--i; i >= 0; --i) { next = Num[i]; printf("%2d", next); } } // Подключаемый файл Dec_16.c // Перевод в шестнадцатеричную систему счисления #include "base. h" void dec16 (long int z) { int i = 0; int Num[64]; int next; long int zz = z; const char digits_16[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; do { Num[i] = z % base16; ++i; z /= base16; } while (z!= 0); // Результат в обратном порядке printf("\n Decimal number \"%ld\" has a hexadecimal equivalent:\n", zz); for (--i; i >= 0; --i) { next = Num[i]; printf("%2c", digits_16[next]); } } // Подключаемый файл base. h // Основания систем счисления #define base2 2 #define base8 8 #define base16 16 // Подключаемый файл hdec. h // Поключение файлов с функциями преобразования систем // счисления #include "Dec_2.c" #include "Dec_8.c" #include "Dec_16.c" // Прототипы функций преобразования систем счисления void dec2 (long int a); void dec8 (long int b); void dec16 (long int c); В программе используется заголовочный файл base. h в целях демонстрации препроцессорных директив языка С. Другой заголовочный файл hdec. h подключает файлы с программами преобразования чисел и прототипы функций. Внимание! Особенность создания h-файлов заключается в том, что они должны заканчиваться пустой строкой (после всех операторов, прототипов функций и пр.) Файлы Dec_2.c, Dec_8.c, Dec_16.c, base. h, hdec. h созданы с помощью инструментальной среды Microsoft Visual Studio 2010 при создании нового файла из меню File–New–File с последующим выбором: либо h-файлы, либо cpp-файлы с последующим установлением расширения языка С, т. е. .с. Созданные файлы следует сохранить в папке с основным программным котодом типа main. c . В программе использован массив указателей на функцию. По определению указатель на функцию содержит адрес первого байта или слова выполняемого кода функции. Над указателями на функцию запрещены арифметические операции. Так же как и для других типов переменных, можно использовать массив указателей на функцию. Соответствующая функция вызывается с указанием индекса требуемой функции. Например: (*fun[0])(number); Десятичное число number передается в функцию dec2(). Нулевой индекс соответствует выбору функции перевода десятичного числа в двоичный эквивалент. Расположение файлов с созданными функциями в основной папке проекта, где находится файл main. c, позволяет подключать необходимые файлы как в текущей папке (директории). Возможный результат выполнения программы показан на рис. 17.6.
Задание 3 Получаемые результаты проверьте с помощью встроенного калькулятора calc операционной системы Windows. Все файлы проекта, кроме main. c, расположите на один уровень выше с помощью нотации../. Дополните программу вывода преобразованного десятичного числа во всех системах счисления одновременно, т. е. по основаниям 2, 8, 16. Видоизмените программу так, чтобы функции преобразования десятичного числа возвращали преобразованное число в основной модуль программного проекта и в нем происходил вывод на консоль.Пример 4. Напишите программу, в которой используется функция формирования накопительной суммы для заданного одномерного целочисленного массива, размерность которого вводится с клавиатуры пользователем. Для пояснения формирования накопительной суммы приведем пример. Пусть задан исходный одномерный массив: Тогда массив накопительной суммы (cumsum – cumulative sum of elements): 8. Программный код решения примера состоит из трех файлов: // Файл main. c // Основной модуль проекта //#include <stdio. h> #include <conio. h> //#include <stdlib. h> #include <locale. h> #include "hcum. h" int main (void) { int i, n; int *M;// Указатель для исходного массива int *N;// Указатель для накопительного массива // Для поддержки русских шрифтов setlocale(LC_ALL, ".1251"); printf("\n Введите размерность одномерного массива: "); scanf_s("%d", &amp;amp;amp;n); M = (int *)calloc(n, sizeof(int)); // Формирование исходного массива for (i = 0; i < n; ++i) M[i] = (i + 1); printf("\n Исходный массив:\n "); for (i = 0; i < n; ++i) if (M[i] < 10) printf("%3d", M[i]); else if (M[i] == 10) printf(" %3d", M[i]); else if (M[i] > 10 &amp;amp;amp;&amp;amp;amp; M[i] < 100) printf("%4d", M[i]); else printf("%5d", M[i]); printf("\n\n Массив накопительной суммы:\n "); // M - фактический аргумент функции cumsum() N = cumsum(M, n); for (i = 0; i < n; ++i) if (N[i] < 10) printf("%3d", N[i]); else if (N[i] == 10 ) printf(" %3d", N[i]); else if (N[i] > 10 &amp;amp;amp;&amp;amp;amp; N[i] < 100) printf("%4d", N[i]); else printf("%5d", N[i]); // Освобождение выделенной памяти free(M); free(N); printf("\n\n\n Нажмите любую клавишу (Press any key): "); _getch(); return 0; } // Подключаемый заголовочный Файл hcum. h #include <stdlib. h> #include <stdio. h> #include <locale. h> // Прототип функции cumsum() int *(cumsum)(int *arr, int n); // Подключаемый файл fcum. c // Функция cumsum() #include "hcum. h" int *(cumsum)(int *arr, int n) { int i; int *SUM;// Указатель для накопительного массива // Выделение памяти для нового указателя SUM = (int *)calloc(n, sizeof(int)); setlocale(LC_ALL, ".1251"); // для русских шрифтов // Проверка выделенной памяти if (SUM == NULL) { printf("\n Память не выделена.\n"); exit(1); } // Основной код формирования накопительной суммы SUM[0] = arr[0]; for (i = 1; i < n; ++i) SUM[i] = SUM[i-1] + arr[i]; return (SUM); } Следует обратить внимание на расположение заголовочного файла stdlib. h – он впереди подключаемого файла fcum. c, поскольку в файле fcum. c используется динамическое распределение памяти, для чего требуется библиотека stdlib. h. Формирование накопительной суммы выполнено в разработанной функции fcum. c. В программе закомментированы библиотечные заголовочные файлы, которые включены в файл hcum. h. Сама накопительная функция cumsum() определена через указатель на функцию. Поэтому она возвращает указатель, который указывает на нулевой элемент массива накопительной суммы. Созданные файлы hcum. h и fcum. c расположены в папке проекта вместе с файлом главной функции main. c. Возможный результат выполнения программы показан на рис. 17.7.
Задание 4 Файлы hcum. h и fcum. c расположите за пределами папки проекта. Применить различные комбинации расположения файлов hcum. h и fcum. c в различных папках данного диска. Обеспечьте работоспособность программы. Внесите изменения в программу, чтобы она была работоспособной без заголовочного файла hcum. h. Напишите программу формирования накопительной суммы вещественных чисел заданного массива. Формирование исходного массива выполните по случайному равномерному закону из интервала [–X; +X], где Х – номер компьютера, на котором выполняется лабораторная работа. Напишите функцию для расчета накопительной суммы столбцов прямоугольной матрицы, размерность которой задается пользователем, и заполняется, например, натуральными числами. Напишите функцию для расчета накопительной суммы строк прямоугольной матрицы, размерность которой задается пользователем, и заполняется, например, натуральными числами. В программе предусмотрите вывод результатов в текстовый файл с именем compX. txt, где Х – номер компьютера, на котором выполняется лабораторная работа.Пример 5. Напишите программу быстрой сортировки Хоара одномерного массива целых чисел с расположением функций в разных файлов [5]. Предусмотрите формирование одномерного массива случайным образом с динамическим распределением памяти. Программный код решения примера состоит из трех файлов: // 1-й файл с главной функцией - файл main. c #include <stdio. h> #include <conio. h> #include <stdlib. h> #include <locale. h> #include <time. h> #include "hsort. h"// Созданный заголовочный файл int main (void) { int i, n; int *M;// Указатель для исходного массива int Limit = 100; time_t t; // переменная текущего времени // Рандомизация генератора псевдослучайных чисел srand( (unsigned int) time(&amp;amp;amp;t)); // Для поддержки русских шрифтов setlocale(LC_ALL, ".1251"); printf("\n\t Быстрая сортировка Хоара\n"); printf("\n Введите размерность одномерного массива: "); scanf_s("%d", &amp;amp;amp;n); // Выделение памяти для заданной размерности массива M = (int *)malloc(n*sizeof(int)); // Формирование случайного исходного массива for (i = 0; i < n; ++i) M[i] = - Limit/2 + rand() % Limit; printf("\n Исходный одномерный массив:\n"); for (i = 0; i < n; ++i) if (abs(M[i]) < 10) { if (M[i] < 0) printf("%4d", M[i]); else printf("%3d", M[i]); } else { if (M[i] < 0) printf("%5d", M[i]); else printf("%4d", M[i]); } printf("\n\n Отсортированный одномерный массив:\n"); QuickSort(M, n); for (i = 0; i < n; ++i) if (abs(M[i]) < 10) { if (M[i] < 0) printf("%4d", M[i]); else printf("%3d", M[i]); } else { if (M[i] < 0) printf("%5d", M[i]); else printf("%4d", M[i]); } printf("\n\n Нажмите любую клавишу: "); _getch(); return 0; } // 2-файл - подключаемый заголовочный файл hsort. h // с прототипом функции быстрой сортировки Хоара void QuickSort(int *A, int n); // 3-й файл - подключаемый файл my_sort. c // с кодом быстрой сортировки Хоара void QuickSort (int *A, int n) { int i, j, s; int L, R; int k, x; #define D 1000 struct stack { int L; int R; } st[D];// имитация стека s = 1; st[1].L = 0; st[1].R = n - 1; do { L = st[s].L; R = st[s].R; s--; do { i = L; j = R; x = A[(L+R)/2]; // разделяющий элемент do { while (A[i] < x) i++; while (x < A[j]) j--; if (i <= j){ k = A[i]; A[i] = A[j]; A[j] = k; i++; j--; }// end if } while (i < j); // end 3d do if (i < R) {s++; st[s].L = i; st[s].R = R;} R = j; } while (L < R);// end 2nd do } while (s!= 0);// end 1st do } Цикл do–while применен для того, чтобы тело цикла выполнялось хотя бы один раз. Возможный результат выполнения программы показан на рис. 17.8.
Задание 5 Произведите сборку проекта из трех предложенных файлов. Напишите программу с сохранением исходного массива и полученного отсортированного массива. Выведите результаты в текстовый файл с именем compX. txt, где Х – номер компьютера, на котором выполняется лабораторная работа. В текстовом файле массивы выведите в виде двух столбцов: исходный массив и отсортированный массив. Размер массивов не менее 10. Измените программу: исключите заголовочный файл hsort. h. Введите изменения в программу для сортировки вещественных чисел.Пример 6. Напишите программу вычисления степенного полинома в заданной точке по схеме Горнера. Пусть задан полином в следующем виде:
Алгоритм схемы Горнера осуществляется при помощи формулы [17.6]:
Полагая, что
Для программной реализации примера примем следующий полином:
Рассчитаем значение полинома в точке Программный код решения примера: #include <stdio. h> #include <conio. h> #include <locale. h> // Размерность массива коэффициентов полинома #define N 5 // Главная функция int main (void) { int i; // Массив коэффициентов полинома double A[ ] = {1.0, 2.0, 3.0, 4.0, 5.0}; double x0 = 2.0, y; // Для русских шрифтов setlocale(LC_ALL, ".1251"); // Для вывода чисел с плавающей точкой setlocale(LC_NUMERIC, "English"); // Консольный заголовок printf("\n\t Применение схемы Горнера\n"); printf(" Вычисление полинома P(x) %d порядка в точке х = %1.4f:\n", N-1, x0); // Основной цикла расчета по схеме Горнера y = A[0]; for ( i = N-2; i >= 0; --i ) y = y*x0 + A[(N-1)-i]; // Вывод результата printf("\n\t P(x) = %0.4f \n", y ); printf("\n Нажмите любую клавишу (Press any key): \a"); _getch(); return 0; } Следует обратить внимание на индексацию массива, который находится в теле цикла. В последней функции printf() использован спецификатор "\a" для подачи звукового сигнала. Результат выполнения программы показан на рис. 17.9.
Задание 6 Примените оператор цикла do – while вместо оператора цикла for. Создайте подключаемый файл, в котором реализуется схема Горнера. Предусмотрите вызов этого файла из главной функции. Предусмотрите ввод коэффициентов полинома с клавиатуры. Далее предусмотрите обращение к созданному файлу с реализацией схемы Горнера. Напишите программу символической записи на консоли заданного полинома по известным коэффициентам. Оформите этот фрагмент программы в виде подключаемого файла. Предусмотрите запись в текстовый файл символического представления полинома с заданными коэффициентами и значения полинома в заданной точке. Имя файла примите compX. txt, где X – номер компьютера, на котором выполняется лабораторная работа.Контрольные вопросыКак рекомендуется организовать внутреннюю работу пользовательских функций по отношению к другим функциям в программах на языке С? Перечислите основные правила организации внутренней работы функций и достоинства этих правил. В чем заключается основное назначение заголовочных файлов (h-файлов) в проектах языка С? Как следует объявить функцию, чтобы доступ к ней был невозможен за пределами файла, где она была определена? Как следует объявить функцию, чтобы к ней можно было обращаться из других функций проекта? Какие классификаторы классов памяти поддерживает стандарт языка С? Какой классификатор памяти используется по умолчанию в программах на языке С? Какие расширения можно применить к файлам, содержащим пользовательские функции? Как осуществляется компиляция файлов с пользовательскими функциями в программной среде Visual Studio? Как осуществляется подключение файлов с пользовательскими функциями, которые расположены на различных логических дисках компьютера? | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Программирование на языке C в Microsoft Visual Studio 2010 | ||||||||||||||||||||||||||
| ||||||||||||||||||||||||||
| ||||||||||||||||||||||||||
| ||||||||||||||||||||||||||
19. Лекция: Препроцессор языка С: | ||||||||||||||||||||||||||
| ||||||||||||||||||||||||||
| ||||||||||||||||||||||||||
| ||||||||||||||||||||||||||
Теоретическая частьПрепроцессор (англ. preprocessor) – программа, выполняющая предварительную обработку входных данных для другой программы [19.1]. Препроцессор языка программирования С просматривает программу до компилятора и заменяет в программе определенные сочетания символов (символические аббревиатуры) на соответствующие директивы. Он отыскивает и подключает к программе необходимые файлы и может изменить условия компиляции [19.1]. Препроцессор имеет тот же смысл, что и буферный процессор. Препроцессор языка С выполняет макроподстановку, условную компиляцию и включение именованных файлов. Строки, начинающиеся со знака # (перед которыми разрешены символы пустого пространства), задают препроцессору инструкции-директивы. Их синтаксис не зависит от остальной части языка; они могут фигурировать где угодно и оказывать влияние (независимо от области действия) вплоть до конца единицы трансляции. Границы строк принимаются во внимание: каждая строка анализируется отдельно (но есть возможность и сцеплять строки). Лексемами для препроцессора являются все лексемы языка и последовательность символов, задающие имена файлов. Кроме того, любой символ, не определенный каким-либо другим способом, также воспринимается как лексема [19.2]. Влияние символов пустого пространства, отличающихся от пробелов и горизонтальных табуляций, внутри строк препроцессора не определено. В предыдущих лабораторных работах уже встречались строки с начальным символом #. Это #include и #define. Первая директива (инструкция) использовалась для подключения заголовочных файлов, в первую очередь из библиотеки языка С, а вторая – для подстановки символов или чисел в определенные места программного кода. Имеются следующие директивы препроцессора:
Каждая директива препроцессора должна занимать отдельную строку. Например, строка #include <stdio. h> #include <stdlib. h> рассматривается как недопустимая [19.3]. Директива #defineДиректива #define определяет идентификатор и последовательность символов, которая будет подставляться вместо идентификатора каждый раз, когда он встретится в исходном файле. Идентификатор называется именем макроса, а сам процесс замены – макрозаменой (макрорасширением, макрогенерацией, макроподстановкой) [19.3]. В общем виде директива #define выглядит следующим образом (должно быть задано буквами латинского алфавита): #define имя_макроса последовательность_символов В определении, как правило, в конце последовательности символов не ставится точки с запятой. Между идентификатором и последовательностью символов последовательность_символов может быть любое количество пробелов, но признаком конца последовательности символов может быть только разделитель строк [19.3]. Имена макросов обычно задаются с помощью букв верхнего регистра. У директивы #define имя макроса может определяться с формальными параметрами. Тогда каждый раз, когда в программе встречается имя макроса, то используемые в его определении формальные параметры заменяются теми аргументами, которые встретились в программе. Такого рода макросы называются макросами с формальными параметрами (макроопределениями с параметрами и макросами, напоминающими функции) [19.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; } В общем случае рекомендуется заключать в круглые скобки формальные параметры, над которыми выполняются те или иные действия. Использование вместо настоящих функций макросов с формальными параметрами (т. е. a, b, с) дает следующее преимущество: увеличивается скорость выполнения кода, потому что в таких случаях не надо тратить ресурсы на вызов функций. Кроме того, макрос не чувствителен к типу данных, т. е. в нем отсутствует проверка типов аргументов. Однако если у макроса с формальными параметрами очень большие размеры, то тогда из-за дублирования кода увеличение скорости достигается за счет увеличения размеров программы [19.3]. И еще, в конструировании макроса следует быть очень внимательным. Как правило, макросы используются для небольших пользовательских функций [19.4]. Директива #errorДиректива #error заставляет компилятор прекратить компиляцию [19.3]. Эта директива используется в основном для отладки. В общем виде директива #error выглядит следующим образом: #error сообщение – об – ошибке Заданное сообщение об ошибке (сообщение – об – ошибке) в двойные кавычки не заключается. Когда встречается данная директива, то выводится сообщение об ошибке – возможно, вместе с другой информацией, определяемой компилятором [19.3]. Директива #includeДиректива #include дает указание компилятору читать еще один исходный файл – в дополнение к тому файлу, в котором находится сама эта директива [19.3]. Имя исходного файла (подключаемого файла) должно быть заключено в двойные кавычки или в угловые скобки. Обычно имена стандартных заголовочных файлов заключают в угловые скобки. А использование кавычек обычно приберегается для имен специальных файлов, относящихся к конкретной программе. Способ поиска файла зависит от того, заключено ли его имя в двойные кавычки или же в угловые скобки. Если имя заключено в угловые скобки, то поиск файла проводится тем способом, который определен в компиляторе. Часто это означает поиск определенного каталога, специально предназначенного для хранения таких файлов. Если имя заключено в кавычки, то поиск файла проводится другим способом. Во многих компиляторах это означает поиск файла в текущем рабочем каталоге. Если же файл не найден, то поиск повторяется уже так, как будто имя фа йла закл ючено в угловые скобки [19.3]. Файлы, имена которых находятся в директивах #include, могут в свою очередь содержать другие директивы #include. Они называются вложенными директивами #include. Количество допустимых уровней вложенности у разных компиляторов может быть разным. Однако в стандарте С89 предусмотрено, что компиляторы должны допускать не менее 8 таких уровней [19.3]. Директивы условной компиляцииДирективы условной компиляции (будут рассмотрены ниже) дают возможность выборочно компилировать части исходного кода программы. Этот процесс называется условной компиляцией [19.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. Особенностью рассмотренных конструкций является то, что проверка выражений осуществляется внутри директив #if и #endif. В соответствии со стандартом С89 у директив #if и #elif может быть не менее 8 уровней вложенности. При вложенности каждая директива #endif, #else или #elif относится к ближайшей директиве #if или #elif [19.3]. Каждая директива #if сопровождается директивой #endif. Директива условной компиляции #ifdef в общем виде выглядит следующим образом: #ifdef имя_макроса последовательность операторов #endif Директива условной компиляции #ifdef означает "if defined" (если определено) [19.3]. Последовательность операторов будет компилироваться, если имя макроса было определено ранее с помощью директивы #define. Директива условной компиляции #ifndef означает "if not defined" (если не определено) в общем виде выглядит следующим образом: #ifndef имя_макроса последовательность операторов #endif Последовательность операторов будет компилироваться, если имя макроса еще не определено директивой #define. В директивах #ifdef и #ifndef можно использовать #else или #elif. Согласно стандарту С89 допускается не менее 8 уровней #ifdef и #ifndef. Директива #undef удаляет заданное определение имени макроса, то есть "аннулирует" его определение; само имя макроса должно находиться после директивы [19.3]. В общем случае директива #undef выглядит следующим образом: #undef имя_макроса Директива #undef используется в основном для того, чтобы локализовать имена макросов в тех участках кода, где они нужны. Для того чтобы узнать определено ли имя макроса, можно использовать директиву #if в сочетании с оператором времени компиляции defined [19.3]. Оператор defined выглядит следующим образом: defined имя_макроса Если имя_макроса определено, то выражение считается истинным; в противном случае – ложным. Единственная причина, по которой используется оператор defined, состоит в том, что с его помощью в #elif можно узнать, определено ли имя макроса [19.3]. Директива #lineДиректива #line изменяет содержимое __LINE__ и __FILE__, которые являются зарезервированными идентификаторами (макросами) в компиляторе. В первом из них содержится номер компилируемой в данный момент строки программного кода программы [19.3]. А второй идентификатор – это строка, содержащая имя компилируемого исходного файла. Директива #line выглядит следующим образом: #line номер "имя_файла" В определении директивы #line обязательным является номер строки, относительно которой будет выполняться подсчет следующих строк. Второй параметр "имя_файла" является не обязательным. Если его не будет, то идентификатор __FILE__ будет содержать путь и имя программы. Если указать в качестве параметра новое имя файла – "имя_файла", то __FILE__ будет содержать это новое имя файла. Директива #line в основном используется для отладки и специальных применений [19.3]. Операторы препроцессора # и ##Операторы # и ## применяются в сочетании с директивой #define [19.3]. Оператор #, который обычно называют оператором превращения в строку (stringize), превращает аргумент, перед которым стоит, в строку, заключенную в кавычки. Оператор # должен использоваться в макросах с аргументами, поскольку операнд после # ссылается на аргумент макроса [19.5]. Оператор ##, который называют оператором склеивания (pasting), или конкатенации конкатенирует две лексемы. Операция ## должна иметь два операнда [19.5]. Операторы # и ## предусмотрены для работы препроцессора в некоторых особых случаях [19.3,19.5]. Директива #pragmaДиректива #pragma – это определяемая реализацией директива, которая позволяет передавать компилятору различные инструкции [19.3]. Она позволяет помещать инструкции компилятору в исходный код [19.4]. Возможности этой директивы следует изучать по документации по компилятору. Предопределенные символические константыВ языке С определены пять встроенных, предопределенных имен макрокоманд [19.2-19.3-19.4-19.5], которые представлены в табл. 19.1.
Макрос подтверждения assertМакрос assert, определенный в заголовочном файле assert. h, проверяет значение выражения [19.5]. Если значение выражения равно 0 (ложное значение), то assert распечатывает (например, выводит на консоль) сообщение об ошибке и вызывает функцию abort() (из библиотеки stdlib. h), завершающую работу программы. Например, в программе переменная х должна принимать значения, не превышающие 10. Для проверки и подтверждения такого условия в программу можно включить следующую строку: assert( x <= 10 ); Если при выполнении данного макроса переменная х окажется больше 10, то выдается сообщение, содержащее номер строки и имя файла (например, main. c), в котором нарушено условие, а выполнение программы при этом прерывается. Формат выводимого сообщения зависит от конкретной реализации системы программирования [19.3]. С помощью assert можно производить отладку программы во многих ее местах. Когда программа будет отлажена, то действие макроса assert можно устранить с помощью символической константы NDEBUG (not debugging – без отладки). Для этого перед заголовочным файлом assert. h следует вставить строку #define NDEBUG #define NDEBUG Практическая частьПример . Напишите программу с использованием макро-функции по определению числа, введенного пользователем, на предмет его простоты. Предусмотрите также вывод на консоль времени компиляции программы и сообщения о реализации языка С. Программный код решения примера: #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 2008. В программе показано применение макроса функции с тремя формальными параметрами и несколькими строками программного кода. Результат выполнения программы приведен на рис. 19.1.
Результат Version C: 1 означает, что компилятор поддерживает стандарт ANSI С. Сделайте так, чтобы признак простого или непростого числа передавался из макроса. Этот признак должен использоваться в функции main(), для вывода соответствующего сообщения на консоль. В программе предусмотрите вывод на консоль общего количества строк программного кода. В программе предусмотрите вывод на консоль даты компиляции и имени компилируемого файла. Напишите макрос с формальными параметрами для проверки на четность целого числа, введенного с клавиатуры. Напишите макрос с формальными параметрами для обмена значениями двух переменных (типа функции swap()). Напишите макрос с формальными параметрами по вычислению площади круга по известному радиусу (вводимого с клавиатуры). Значение числа _ определить с 15 знаками после десятичной точки при использовании директивы #define. Введите в программу вычисление случайных чисел, равномерно распределенных в интервале [0; Х], где Х – номер компьютера (1, 2, 3, ...), на котором выполняется лабораторная работа. Количество случайных чисел должно соответствовать числу секунд (не равных нулю), определяемых с помощью символической константы __TIME__.Пример 2. Выполните проверку подключаемого тестового файла и вывести на консоль содержимого этого файла. Содержимое файла – стихотворный пример бесконечной рекурсии: у попа была собака.... Программный код решения примера: // Файл с главной функцией main() #define _CRT_SECURE_NO_WARNINGS #include <stdio. h> #include <conio. h> #include <stdlib. h> #include <locale. h> // Подключение текстового файла #ifndef AZA #define AZA #include "dog. txt" #endif int main (void) { short i, j, n, in; i = j = 0; // для поддержки русских шрифтов setlocale (LC_ALL, "rus"); printf("\n Введите количество стихотворных строф: "); in = scanf("%hd", &n); if (in!= 1 || n < 1) { printf("\n Ошибка ввода данных. Нажмите любую клавишу: "); _getch(); exit(1); } // Условие распечатки текстового файла #ifdef AZA puts(""); for (j = 0; j < n; j++ ) { i = 0; while (d[i] != NULL) { printf(" "); puts(d[i]); i++; } } #endif printf("\n... Нажмите любую клавишу: "); _getch(); return 0; } Содержимое текстового файла dog. txt: char *d[] = { "У попа была собака,", \ "Он её любил,", \ "Она съела кусок мяса,", \ "Он её убил...",\ "Вырыл ямку, закопал,", \ "На дощечке написал:\n" }; Решение примера выполнено в виде двух файлового проекта. Инициализация переменных в главной функции сделано на случай, если не будет определена директива #define AZA, чтобы не было предупреждений компилятора о неиспользованных переменных i и j. Пример выполнения программы показан на рис. 19.2.
Задание 2 Стихотворение запишите в текстовый файл с именем compX. txt, где Х – номер компьютера, на котором выполняется лабораторная работа. Вместо препроцессорной директивы #ifdef примените другую директиву условной компиляции. В программу включите директиву #else. Какой препроцессорной директивой можно исключить из программы именованную константу AZA? Создайте файл dog. h с содержимым файла dog. txt и подключите его к проекту вместо файла dog. txt. Напишите "чистую" рекурсивную функцию для распечатки стихотворения о попе и его собаке. В качестве аргумента функции включите количество стихотворных строф. Подсчитайте количество рекурсивных вызовов.Пример 3. С помощью директив условной компиляции и символической константы _DEBUG напишите программу ввода слов с клавиатуры с проверкой возможности компиляции программного кода. Программный код решения примера: #include <stdio. h> #include <conio. h> int main (void) { char str[80]; // Начало проверки компилируемого кода #ifdef _DEBUG printf("\n Start debugging\n"); #endif do { printf("\n Enter a word or \"z\" to exit: "); gets_s(str, 79); #if _DEBUG printf("\n The word is \"%s\"\n", str ); #else #error This version is not to the C Run-Time Library. \ Break to debugging. #endif } while (str[0] != 'z' && str[0] != 'Z'); printf("\n\n... Press any key: "); _getch(); return 0; } Символическая константа _DEBUG будет определяться (существовать) в режиме Debug, которое находится в списке главного меню интегрированной системы MS Visual Studio 2010. На рис. 19.3 показан выбор режима отладки Debug.
Пример выполнения программы показан на рис. 19.4.
Задание 3 Внесите изменения в программу, чтобы условие о невозможности компиляции было реализовано без директивы #error. Вместо специализированной константы _DEBUG введите собственную символическую константу COMP_X, где Х – номер компьютера, на котором выполняется лабораторная работа. Напишите программу со стеком, в который будут помещаться вводимые слова, а после предусмотреть возможность извлечения набранных слов (по дисциплине LIFO).Пример 4. С помощью директивы #define и оператора препроцессора # напишите программу определения кода вводимого символа. Программный код решения примера: #define _CRT_SECURE_NO_WARNINGS #include <stdio. h> #include <conio. h> #include <locale. h> #define CHAR_COD(c) ""#c"" int main (void) { char ch; setlocale (LC_ALL, "rus"); printf("\n Введите символ: "); scanf("%c", &ch); printf("\n %s \"%c\", его код %d", CHAR_COD(Символ), ch, ch); printf("\n\n... Нажмите любую клавишу: "); _getch(); return 0; } Оператор # должен использоваться в макросах с аргументами, поскольку операнд после ссылается на аргумент макроса [19.5]. В данном случае строка "Символ" подставляется в заменяющий текст вместо #c. Пример выполнения программы показан на рис. 19.5.
Задание 4 Напишите программу повторного ввода символов с клавиатуры до тех пор, пока не будет введен символ "z". Выполните макроподстановку со строкой "compX" и присоединяемой строкой "Х" с помощью функции printf(), где Х – номер компьютера, на котором выполняется лабораторная работа.Пример 5. С помощью директивы #define и оператора препроцессора ## классифицируйте четность или нечетность кода вводимого символа. Программный код решения примера: #define _CRT_SECURE_NO_WARNINGS #include <stdio. h> #include <conio. h> #include <locale. h> // LEXEME - лексема #define TWO_LEXEME(a, b) a ## b #define CHAR_COD(c) ""#c"" int main (void) { char ch; setlocale (LC_ALL, "rus"); printf("\n Введите символ: "); scanf("%c", &ch); if (!(ch % 2)) printf("\n %s \"%c\", его код %d %s", \ CHAR_COD(Символ), ch, ch, TWO_LEXEME(" - четный","!")); else printf("\n %s \"%c\", его код %d %s", \ CHAR_COD(Символ), ch, ch, TWO_LEXEME(" - не ", "четный.")); printf("\n\n... Нажмите любую клавишу: "); _getch(); return 0; } Данная программа – некоторое расширение предыдущей программы. Операция конкатенации строк ## осуществляется с помощью макроса TWO_LEXEME() с двумя аргументами. Пример выполнения программы показан на рис. 19.6.
Задание 5 В программе примените только буквы латинского алфавита. Для записи четности или нечетности введенного символа предусмотрите массив символов str1 и str2. Произведите конкатенацию двух строк "comp" и "Х" с помощью операции ##, где Х – номер компьютера, на котором выполняется лабораторная работа.Пример 6. Напишите программу формирования структуры с информацией о студенте (об учащемся) и с помощью директивы #pragma осуществить оптимизацию использования памяти, выделяемой под структуру. Для решения примера используем справку системы Visual Studio 2010 (для этого из меню Help Программный код решения примера: #include <stdio. h> #include <conio. h> #pragma pack (show) struct Student { char name[21]; // имя, фамилия int age; // возраст, полных лет char gender; // пол, мужской, женский double mean; // средний балл успеваемости } st1; #pragma pack (push) #pragma pack (1) #pragma pack (show) struct Student_pack { char name[21]; // имя, фамилия int age; // возраст, полных лет char gender; // пол, мужской, женский double mean; // средний балл успеваемости } st2; #pragma pack (pop) #pragma pack (show) int main (void) { puts(""); printf (" sizeof (struct st1) = %d\n", sizeof (st1)); printf (" sizeof (struct st2) = %d\n", sizeof (st2)); printf("\n... Press any key: "); _getch(); return 0; } В программе опция pack(show) используется для диагностики текущей упаковки полей структуры. Опция pack(1) используется для выравнивания областей памяти полей структуры, кратных единице. Опция pack(push) используется для "вталкивания" параметров. Опция pack(pop) используется для "выталкивания" параметров. После компиляции программы можно видеть сообщения, показанные на рис. 19.7.
Предупреждения (warning) уведомляют о размере упаковки. Сначала область памяти выделяется под размер в байтах, кратных 8, затем, кратных 1. После выталкивания параметров снова область памяти становится кратной 8 – наибольшему типу данных double элемента mean структуры. Результат выполнения программы показан на рис. 19.8.
Как видно, размер первой структуры равно 40 байтов (кратных 8). Размер второй структуры, для которой произведена упаковка, равен 34, что соответствует сумме размеров полей структуры, т. е. 34 = 21 + 4 + 1 + 8. Примечание. Выравнивание полей структуры можно выполнить при настройке компилятора в MS Visual Studio 2010. Эта настройка показана на рис. 19.9.
Задание 6 Запишите в двоичные файлы созданные структуры, проведя их инициализацию. Определите и сравните размеры созданных бинарных файлов. Произведите также чтение данных из бинарных файлов с выводом результата на консоль. В программе для аргумента pack() примите 1, 2, 4, 8, 16. Объясните результаты выполнения программы. Размер массива типа char примите равным 31, т. е. char name[31]. Объясните результат выполнения программы. В качестве элементов структуры используйте следующие типы данных:float, short int. Объясните результат выполнения программы. Используя меню ProjectКонтрольные вопросыКакое назначение отводится препроцессору языка С? Что такое условная компиляция, производимая препроцессором? В каких целях производится условная компиляция? Назовите операторы препроцессора. Для чего они используются? Какие директивы препроцессора используются наиболее часто в программах, написанных на языке С? Что такое макроопределение препроцессора? Как оно реализуется? | ||||||||||||||||||||||||||
| ||||||||||||||||||||||||||
Программирование на языке C в Microsoft Visual Studio 2010 | ||||||||||||||||||||||||||
| ||||||||||||||||||||||||||
| ||||||||||||||||||||||||||
| ||||||||||||||||||||||||||
20. Лекция: Программы на языке С при использовании статически подключаемой библиотеки: | ||||||||||||||||||||||||||
| ||||||||||||||||||||||||||
| ||||||||||||||||||||||||||
| ||||||||||||||||||||||||||
Теоретическая часть Библиотека представляет собой набор функций [20.1]. Когда программа использует библиотечную функцию, редактор связей находит эту функцию и добавляет ее код в программу. Исполняемый файл содержит только те функции, которые используются программой, а не все библиотечные функции [20.1]. Статически подключаемая библиотека содержит набор уже откомпилированных объектных файлов с функциями и данными. Библиотеки целесообразно применять для хранения функций, которые могут быть использованы при создании различных программ, реализующих распространенные алгоритмы и осуществляющих поддержку и обработку распространенных структур данных. Библиотека называется статически подключаемой, если код, содержащийся в ней, непосредственно компонуется к основной программе. Механизм компиляции и компоновки программы на языке C требует, помимо наличия откомпилированного библиотечного модуля, присутствия заголовочных файлов (h-файлов), содержащих объявления структур данных и прототипы функций, предоставляемых библиотекой. Среда Visual Studio 2010 использует расширение. lib для библиотечных модулей. При создании статически подключаемой библиотеки в среде Visual Studio 2010 необходимо выполнить следующую последовательность действий. Создать новый проект (пункты главного меню: File–New–Project), выбрать тип проекта в списке Project types: Win32–Win32Project и задать имя проекта, например, containers. При необходимости можно указать место его расположения, используя кнопку Browse. В результате должна получиться форма, показанная на рис. 20.1.
Далее следует нажать кнопку OK. Появится форма С заголовком "Win32 Application Wizard – containers. На закладке Application Settings мастера создания проекта сделать следующие настройки:
После установки настроек появится форма (рис. 20.2), которая представляет собой пустой проект статической библиотеки.
Для завершения настройки закладки Application Settings следует нажать кнопку Finish. Появится форма, показанная на рис. 20.3.
Добавление файлов в проект библиотеки осуществляется стандартным образом, как и для проекта Win32 Console Application. В соответствии с рис. 20.3существующие файлы, которые будут использоваться в многофайловом проекте, могут быть подключены при установки курсора мыши на папках Header Files, Resource Files, Source Files с последующим нажатием правой клавиши и выбором пункта меню Add, а именно Existing Item. Для подключения h-файлов, т. е. *.h, следует обратиться к папке проекта Header Files, для подключения к проекту с-файлов, т. е. *.с, использовать папку Source Files. Выполним подключение существующих файлов stack. h/stack. c, queue. h/queue. c, реализующие в простейшем виде две важные структуры данных – стек и очередь. Так как статическая библиотека не является исполняемой программой, а только механизмом для хранения подпрограмм, то среди функций библиотеки не должно быть функции main(). После подключения файлов получится форма, показанная на рис. 20.4, с открытой программой файла stack. h.
До выполнения компиляции необходимо выполнить настройку проекта. Настройка параметров компилятора выполняется так же, как и для проекта Win32 Console Application. В частности, из пункта меню Project следует выбрать containers Properties (Alt+F7). После раскрытия узла Configuration Properties появится форма, показанная на рис. 20.5.
Сначала следует обратиться к пункту General. Затем, к закладке Character Set, в которой выбрать Use Multy-Byte Character Set (как и при настройке консольного приложения). Далее необходимо раскрыть узел С/С++, в котором следует обратиться к закладке Code Generation, затем в другой панели закладка Enable C++ Exceptions устанавливается в положение No (как и при настройке консольного приложения). Результат установки свойств следующей закладки Language показан на рис. 20.6.
Режим работы языка С в MS Visual Studio устанавливается на закладке Advanced. Результат установки показан на рис. 20.7.
Важным моментом, на который требуется обратить внимание, является версия используемой библиотеки времени выполнения (runtime library). Библиотека времени выполнения содержит функции стандартной библиотеки языка С, а также некоторое вспомогательное окружение, которое позволяет программе, написанной на языке С, выполняться в ОС Windows. Версия библиотеки времени выполнения для статически подключаемой библиотеки и для программы, ее использующей, должны совпадать. По этой причине статически подключаемую библиотеку часто компилируют в различных конфигурациях, каждая из которых использует свою версию библиотеки времени выполнения. В примере будем использовать многопоточную отладочную версию библиотеки времени выполнения, подключаемую к программе динамически (Multi-threaded Debug DLL) для отладочной сборки библиотеки, и многопоточную версию подключаемую динамически (Multi-threaded DLL), для конечной версии программы. Тип используемой библиотеки времени выполнения выбирается на странице свойств [C/C++]|[Code? Generation]|[Runtime? Library]. Результат выбора типа библиотеки показан на рис. 20.8 (по умолчанию).
Подключение программных файлов осуществляется обычными средствами, рассмотренными, например, в лекции 17. Добавим к проекту статической библиотеки файлы stack. h/stack. c и queue. h/queue. c, реализующие в простейшем виде две важные структуры данных – стек и очередь. Подключение программных файлов осуществляется обычными средствами, рассмотренными, например, в предыдущей теме. Программный код каждого из подключенных файлов можно вывести (двойным щелчком мыши) в окно редактирования. Для примера выведем код файла stack. h. Результат подключения файлов файлы stack. h/stack. c и queue. h/queue. c, показан на рис. 20.9.
После выполнения настроек компилятора необходимо выполнить настройки библиотекаря (Librarian) на вкладке после узла С/С++. Страница свойств библиотекаря содержит несколько настроек, из которых основной является имя создаваемой библиотеки. По умолчанию имя библиотеки совпадает с именем проекта. В этом случае следует оставить без изменения имеющиеся настройки закладки General узла Librarian. На закладке General в пункте Output File по умолчанию установлено $OutDir$(TargetName)$(TargetExt), что оставляем без изменения. Открывающаяся страница свойств показана на рис. 20.10.
Настройка свойств Librarian завершается нажатием клавиш "Применить" и "OK". После завершения настроек выполняется компиляция и сборка библиотеки. В процессе компиляции модули, входящие в состав библиотеки (файлы с расширением. c), сначала обрабатываются препроцессором языка C, затем компилируются независимо друг от друга. В результате компиляции получается набор файлов с расширением. obj, содержащих скомпилированный код библиотечных функций. Затем полученный набор объектных файлов (с расширением. obj) объединяется в библиотечный модуль (файл с расширением. lib). Процесс сборки проекта статической библиотеки запускается из пункта меню Build – Build containers. Результат выбора показан на рис. 20.11.
В процессе сборки библиотеки компилятор и библиотекарь (Librarian) выводят в окно сообщений (Output) среды Visual Studio диагностическую информацию. Эти сообщения содержат результаты компиляции каждого из модулей, подключенных к проекту статической библиотеки, возможные предупреждения компилятора и конечную статистику (например, количество ошибок и предупреждений) сборки статической библиотеки. Сообщение о результатах компиляции рассматриваемого примера показаны на рис. 20.12.
В результате произведенной компиляции получаем папку с именем созданной библиотеки (containers), в которой в папке Debug располагается двоичный объектный библиотечный модуль – файл с расширением. lib. Для данного случая это файл containers. lib. Программные коды подключаемых файлов: // file stack. h #ifndef STACK_H__ #define STACK_H__ /// by default a stack reserves space for 16 items #define STACK_INITIAL_CAPACITY 16 typedef struct stack { /// number of items in the stack int m_length; /// capacity of the stack int m_capacity; /// block of memory for the stack int *m_items; } stack_t; /// create a new stack and returns it stack_t *stack_create (int capacity); /// destroys the stack and frees resources void stack_destroy (stack_t *stack); /// pushes an item into the stack int stack_push (stack_t *stack, int item); /// pops the item from the stack int stack_pop (stack_t *stack); /// checks whether the stack is empty int stack_is_empty (stack_t *stack); #endif // file stack. c #include <assert. h> #include <malloc. h> #include <stddef. h> #include "stack. h" static int stack_ensure_capacity (stack_t *stack, int capacity) { int capacityDesired; int *p; if (stack->m_capacity >= capacity) return 1; capacityDesired = stack->m_capacity * 2; p = realloc (stack->m_items, capacityDesired * sizeof (int)); if (!p) return 0; stack->m_items = p; stack->m_capacity = capacityDesired; return 1; } stack_t* stack_create (int capacity) { stack_t *result; if (capacity <= 0) capacity = STACK_INITIAL_CAPACITY; result = malloc (sizeof (stack_t)); if (!result) return NULL; result->m_items = malloc (capacity * sizeof (int)); if (!result->m_items) { free (result); return NULL; } result->m_capacity = capacity; result->m_length = 0; return result; } void stack_destroy (stack_t *stack) { assert (stack!= NULL); assert (stack->m_items!= NULL); free (stack->m_items); free (stack); } int stack_push (stack_t *stack, int item) { assert (stack!= NULL); assert (stack->m_capacity > 0); assert (stack->m_items!= NULL); if (!stack_ensure_capacity (stack, stack->m_length + 1)) return 0; stack->m_items[stack->m_length++] = item; return 1; } int stack_pop (stack_t *stack) { assert (!stack_is_empty (stack)); return stack->m_items[--stack->m_length]; } int stack_is_empty (stack_t *stack) { assert (stack!= NULL); return stack->m_length <= 0; } // file queue. h #ifndef QUEUE_H__ #define QUEUE_H__ typedef struct queue_item { /// pointer to the next item in the queue struct queue_item *m_next; /// item data int m_item; } queue_item_t; typedef struct queue { /// number of items in the queue int m_length; /// first item in the queue struct queue_item *m_head; /// last items in the queue struct queue_item **m_tailnext; } queue_t; /// creates a new queue and returns it queue_t *queue_create (); /// destroys the queue and frees resources void queue_destroy (queue_t *queue); /// pushes an item into the queue adding it to the queue's tail int queue_push (queue_t *queue, int item); /// pops the item from the queue, removing it from the queue's head int queue_pop (queue_t *queue); /// checks whether the queue is empty int queue_is_empty (queue_t *queue); #endif // file queue. c #include <assert. h> #include <malloc. h> #include <stddef. h> #include "queue. h" queue_t* queue_create () { queue_t *queue; queue = malloc (sizeof (queue_t)); if (!queue) return NULL; queue->m_head = NULL; queue->m_tailnext = &(queue->m_head); queue->m_length = 0; return queue; } void queue_destroy (queue_t *queue) { queue_item_t *p; assert (queue!= NULL); for (p = queue->m_head; p!= NULL; p = p->m_next) free (p); free (queue); } int queue_push (queue_t *queue, int item) { queue_item_t *p; assert (queue!= NULL); assert (queue->m_tailnext!= NULL); // create new queue item and insert it into tail p = malloc (sizeof (queue_item_t)); if (!p) return 0; p->m_next = NULL; p->m_item = item; *queue->m_tailnext = p; queue->m_tailnext = &(p->m_next); ++queue->m_length; return 1; } int queue_pop (queue_t *queue) { queue_item_t *p; assert (!queue_is_empty (queue)); // detach head and return the item p = queue->m_head; if (p) { int item = p->m_item; queue->m_head = p->m_next; // if the last one was removed than // we should reset our tail if (queue->m_tailnext == &(p->m_next)) queue->m_tailnext = &(queue->m_head); free (p); --queue->m_length; assert (queue->m_length >= 0); return item; } assert (1 != 1); // should not happen return 0; } int queue_is_empty (queue_t *queue) { assert (queue!= NULL); return queue->m_length <= 0; } Для работы с созданной библиотекой следует создать проект с главной функцией main(), в которой подключаются файлы, расположенные в созданной статической библиотеке, с помощью директив #include. Для подключаемых файлов необходимо указать путь, где они расположены. Обычно это делается с помощью нотации "..\..\stack. h", которая указывает, что файл stack. h расположен на два уровня выше, чем функция main(), в которой он будет использоваться. Настройка проекта с главной функцией main() выполняется при установке режима компиляции языка С системы MS Visual Studio 2010. Для этого в меню системы MS Visual Studio последовательно выбирается File – New – Project. Далее из списка типа проекта Project types также последовательно выбираются Visual C++ – Win32– Win32 Console Application. Прописывается в поле Name: имя проекта, например, Lab20. Далее осуществляется настройка проекта в режиме компиляции языка С (см. Тему 1 данного пособия). При настройке параметров компилятора дополнительно необходимо указать компилятору пути к заголовочным файлам stack. h и queue. h, содержащие объявления интерфейса созданной библиотеки containers. Эти пути можно указать в пункте Additional Include Directories (дополнительные каталоги с заголовочными файлами) на странице свойств [C/C++]|[General] Указывается путь к папке containers, в которой находятся библиотечные файлы stack. h/stack. с и queue. h/queue. с в виде..\..\containers\ containers. Форма с установкой пути к созданной статической библиотеке показана на рис. 20.13.
Важным моментом, на который требуется обратить внимание, является версия используемой библиотеки времени выполнения (runtime library). Библиотека времени выполнения содержит функции стандартной библиотеки языка С, а также некоторое вспомогательное окружение, которое позволяет программе, написанной на языке С, выполняться в ОС Windows. Версия библиотеки времени выполнения для статически подключаемой библиотеки и для программы, ее использующей, должны совпадать. По этой причине статически подключаемую библиотеку часто компилируют в различных конфигурациях, каждая из которых использует свою версию библиотеки времени выполнения. В нашем примере будем использовать многопоточную отладочную версию библиотеки времени выполнения, подключаемую к программе динамически (Multi-threaded Debug DLL) для отладочной сборки нашей библиотеки, и многопоточную версию, подключаемую динамически (Multi-threaded DLL) для конечной версии программы. Тип используемой библиотеки времени выполнения выбирается на странице свойств [C/C++]|[Code Generation]|[Runtime Library]. Он должен совпадать с типом, выбранным при настройке свойств созданной библиотеки, в данном случае Multy-threaded Debug DLL(\MDd). После настройки параметров компилятора необходимо выполнить настройку параметров компоновщика (Linker). На этапе компоновки происходит подключение статической библиотеки, из нее извлекается код уже скомпилированных функций, которые используются в основном проекте (с главной функцией main()). Кроме кода функций, компоновщик при необходимости извлекает из статической библиотеки совместно используемые глобальные переменные. После того как все ссылки на функции и переменные будут разрешены, компоновщик выполняет вычисление машинных адресов для функций и переменных в конечном исполняемом модуле. На странице свойств [Linker]|[Input] необходимо указать путь к объектному файлу библиотеки. Форма с установкой свойств компоновщика показана на рис. 20.14.
После выполнения всех настроек можно компилировать программу и запускать ее на выполнение. Проект с главной функцией main() и включенными заголовочными файлами из созданной библиотеки показан на рис. 20.15.
Программный код главной функции проекта: #include <stdio. h> #include <conio. h> #include "..\..\stack. h" #include "..\..\queue. h" int main (void) { int i; queue_t *q = queue_create(); stack_t *s = stack_create(-1); for (i = 0; i < 16; ++i) //Заполнение стека stack_push (s, i); printf("\n Stack content:\n"); while (!stack_is_empty (s)) printf (" %3d\n", stack_pop (s)); stack_destroy (s); //Разрушение стека for (i = 0; i < 14; ++i) //Заполнение очереди queue_push (q, i); printf("\n Queue content:\n"); while (!queue_is_empty (q)) printf (" %3d\n", queue_pop (q)); queue_destroy (q); //Разрушение очереди printf("\n\n Press any key: "); _getch(); return 0; } Результат выполнения программы показан на рис. 20.16.
Задание Предусмотрите ввод чисел, помещаемых в очередь, с клавиатуры. Предусмотрите ввод чисел, помещаемых в стек, с клавиатуры. Ввод чисел выполните до заданного наперед символа, например, первой буквы фамилии пользователя. Видоизмените проект для хранения в стеке (и вывода содержимого на консоль) данных типа char.Практическая часть Пример 1. Разработайте библиотечную функцию для симметричного представления одномерного массива данных относительно первого значения, например, пусть дан исходный одномерный массив . Результат симметричного представления: 25 Программный код решения примера состоит из двух файлов: // Файл основного модуля проекта main. c #include <stdio. h> #include <conio. h> #include <stdlib. h> #include "xyx. h" int main (void) { int i, n = 7; int M[] = {3, 5, 1, 8, 12, 21, 25}; printf("\n Initial array:\n"); for (i = 0; i < n; ++i) printf(" %3d", M[i]); printf("\n\n New array:\n"); for (i = 0; i < (2*n-1); ++i) printf(" %3d", *(xyx(M, n)+i)); printf("\n\n Press any key: "); _getch(); return 0; } // Подключаемый заголовочный файл xyx. h // file xyx. h int *xyx(int M[], int n); // Подключаемый файл xyx. c #include <stdlib. h> int *xyx(int M[], int n) { int j, p = 2*n - 1; int *PTR; PTR = (int *)calloc(p, sizeof(int)); for (j = 0; j < p; ++j) PTR[j] = 0; for (j = 0; j < p; ++j) if (j < n) PTR[j] = M[(n-1) - j]; else PTR[j] = M[j - (n-1)]; return PTR; } Результат выполнения программы показан на рис. 20.17.
Задание 1 К проекту подключите статическую библиотеку с файлами xyx. h, xyx. c. Осуществите сборку проекта из приведенных файлов. Предусмотрите ввод чисел массива с клавиатуры. Напишите функцию типа xyx(), которая обрабатывает одномерные символьные массивы данных. Видоизмените программу для преобразования массива с действительными числами, которые формируются случайным образом из данного интервала [–2*X;2*X], где Х – номер компьютера, на котором выполняется лабораторная работа. Разработайте функцию сортировки чисел массива, поместите ее в статическую библиотеку, и используйте для сортировки заданного массива по убыванию в соответствии с предыдущим пунктом задания. После сортировки произведите преобразование массива с помощью созданной библиотечной функции xyx().Пример 2. Разработайте абстрактный тип данных – двоичное дерево поиска. Выполните вставки узлов в двоичное дерево случайными числами и произведите обход дерева с порядковой выборкой [20.2]. Созданные функции заполнения и обхода двоичного дерева поместите в статическую библиотеку. Дерево – это нелинейная двухмерная структура данных с особыми свойствами. Узлы дерева две или более связей. В двоичном дереве узлы содержат две связки. Первый узел дерева называется корневым. Каждая связь корневого узла ссылается на потомка. Левый потомок – первый узел левого поддерева, правый потомок – первый узел правого поддерева. Потомки одного узла называются узлами-сиблингами. Узел, не имеющий потомков, называется листом. Двоичное дерево поиска (с неповторяющимися значениями в узлах) устроено так, что значения в любом левом поддереве меньше, чем значение в родительском узле, а значения в любом правом поддереве больше, чем значение в родительском узле [20.2]. На рис. 20.18 изображена схема двоичного дерева поиска с 12 значениями.
В программах, реализующих стеки, очереди, деревья и т. д., используются автореферентные структуры (self-referential), которые содержат в качестве элемента указатель, который ссылается на структуру того же типа. Например, определение struct node { int data; struct node *nextPtr; }; описывает тип struct node. Элемент nextPtr указывает на структуру типа struct node – структуру того же самого типа, что и объявленная структура, т. е. ссылается сама на себя. Для заданного примера используем целые случайные числа из интервала от 0 до 14. Программный код решения примера, состоящий из трех файлов: // Файл основного модуля проекта main. c #include <stdio. h> #include <conio. h> #include <stdlib. h> #include <time. h> #include <locale. h> //#include "tree. h" #define N 10 // количество случайных чисел - узлов #define R 15 // случайные числа от 0 до R-1 // Автореферентная структура struct treeNode { struct treeNode *LeftPtr; //для левого поддерева int data; struct treeNode *RightPtr; // для правого поддерева }; typedef struct treeNode TreeNode; typedef TreeNode *TreeNodePtr; // Прототипы функций void insertNode (TreeNodePtr *treePtr, int value); void inOrder(TreeNodePtr treePtr); int main (void) { int i; int item; time_t tic; TreeNodePtr rootPtr = NULL; // пустое дерево setlocale(LC_ALL, ".1251"); // русские шрифты srand((unsigned) time(&tic)); // рандомизация случайных чисел printf("\n Числа двоичного дерева:\n"); // Размещение в дереве случайных значений от 0 до (R-1) for (i = 1; i <= N; ++i) { item = rand() % R; printf(" %4d", item); insertNode (&rootPtr, item); } // Обход дерева с порядковой выборкой printf("\n"); printf("\n Результат обхода дерева с порядковой выборкой:\n"); inOrder(rootPtr); // вызов функции printf("\n\n Нажмите любую клавишу (Press any key): "); _getch(); return 0; } // Функция insertNode() // Вставка узла в дерево void insertNode (TreeNodePtr *treePtr, int value) { if (*treePtr == NULL) { *treePtr = malloc(sizeof(TreeNode)); // присвоение данных if (*treePtr!= NULL) { (*treePtr)->data = value; (*treePtr)->LeftPtr = NULL; (*treePtr)->RightPtr = NULL; } else { printf(" %d не вставлено. Нет памяти.\n", value); } } else { //когда дерево не пусто if ( value < (*treePtr)->data ) insertNode (&( (*treePtr)->LeftPtr), value); else if( value > (*treePtr)->data ) insertNode (&( (*treePtr)->RightPtr), value); else printf("Дубл."); // Дубликаты значений в узлах дерева } } // Функция inOrder() // Обход дерева с порядковой выборкой void inOrder (TreeNodePtr treePtr) { if (treePtr!= NULL) { inOrder(treePtr->LeftPtr); printf(" %4d", treePtr->data); inOrder(treePtr->RightPtr); } } Функции insertNode(), inOrder() используются рекурсивно, т. е. они вызывают сами себя из тела функции. В теле функции inOrder() выполняются следующие шаги: Обойти вызовом inOrder() левое поддерево. Обработать значение в узле. Обойти вызовом inOrder() правое поддерево.Обход двоичного дерева поиска вызовом функции inOrder() выдает значения в узлах в возрастающем порядке. Процесс создания двоичного дерева поиска фактически сортирует данные, поэтому называется сортировкой двоичного дерева [20.2]. Возможный результат работы программы показан на рис. 20.10.
Задание 2 Функции заполнения и обхода двоичного дерева поместите в статическую библиотеку. Выполните настройки проекта с подключаемой статической библиотекой. Напишите программу с использованием вещественных чисел, помещаемых в узлы двоичного дерева. Случайные числа (значения узлов дерева) задайте из интервала [–2*X;2*X], где Х – номер компьютера, на котором выполняется лабораторная работа. Увеличьте число узлов дерева до 11*Х, где Х – номер компьютера, на котором выполняется лабораторная работа. Результат выполнения программы запишите в текстовый файл вида treeX. txt, где Х – первая буква фамилии пользователя.Контрольные вопросы Какая библиотека называется статически подключаемой? Какую нотацию рекомендуется использовать для созданных пользователем библиотечных модулей? Какое расширение используется для созданных пользовательских библиотечных модулей? Применяется или нет функция main() в статически подключаемой библиотеке, созданной пользователем? По какой дисциплине происходит обработка данных в такой структуре данных, как стек? По какой дисциплине происходит обработка данных в такой структуре данных, как очередь? | ||||||||||||||||||||||||||
| ||||||||||||||||||||||||||
Программирование на языке C в Microsoft Visual Studio 2010 | ||||||||||||||||||||||||||
| ||||||||||||||||||||||||||
| ||||||||||||||||||||||||||
| ||||||||||||||||||||||||||
21. Лекция: Использование аргументов командной строки в С: | ||||||||||||||||||||||||||
| ||||||||||||||||||||||||||
| ||||||||||||||||||||||||||
| ||||||||||||||||||||||||||
Теоретическая часть Аргумент командной строки – это информация, которая вводится в командной строке операционной системы вслед за именем программы [21.1]. В системных средах, поддерживающих язык программирования С, существует способ передавать в программу аргументы или параметры командной строки при запуске программы на выполнение [21.2]. Для этого в главную функцию main() включают два аргумента, обычно argc и argv. Первый (от argument count – счетчик аргументов) содержит количество аргументов командной строки, с которыми была запущена программа. Второй (от argument vector – вектор аргументов) указывает на массив символьных строк, содержащих сами аргументы, – по одному в строке. В общем случае имена аргументов могут быть произвольными. Формально можно определить следующий прототип функции main() с параметрами: int main (int argc, char *argv[]); Второй параметр функции main() выражает собой многоуровневую систему указателей. В связи с этим можно определить другой способ задания параметров функции main(), а именно int main (int argc, char **argv); Каждый указатель на значение типа char ссылается на одну из строк командной строки, при этом argv[0] указывает на имя команды (исполняемой программы), argv[1] – на первый аргумент командной строки, argv[2] – на второй аргумент и так далее [21.3]. В качестве принимаемых аргументов командной строки могут быть исполняемые файлы. Можно из программы запустить на выполнение другую программу, запустить новый процесс. Для этого существуют специальные функции библиотеки "C Run–Time Library Reference" системы Visual Studio (которую используем в качестве компилятора языка С). Командная оболочка операционной системы Windows использует интерпретатор команд cmd. exe, который загружает приложения и направляет поток данных между приложениями, для перевода введенной команды в понятный системе код. Консоль командной строки присутствует во всех версиях операционных систем Windows. В среде разработки MS Visual Studio 2010 аргументы командной строки можно задать в самой среде, для чего следует произвести соответствующую настройку. Например, предположим, что мы хотим создать проект под именем tcmd, чтобы он принимал аргументы командной строки, например, "Example 1" и E:\forma. exe. Для этого после (или во время) создания проекта обратиться к закладке, показанной на рис. 21.1.
Параметры или аргументы командной строки должны вводиться через пробелы. В случае, если параметр представляет собой строку с пробелами, то его следует взять в двойные кавычки. Использование указанной возможности интегрированной среды разработки позволяет произвести отладку программы с аргументами командной строки. В дальнейшем задание параметров в соответствии с рис. 21.1 не обязательно. Программа должна сама подхватывать аргументы командной строки, определенные с помощью утилиты cmd. exe (просто cmd) операционной системы. Командную строку операционной системы Windows можно запустить следующими способами: ПускПараметры, представленные на рис. 21.1, можно ввести из командной строки операционной системы Windows, как показано на рис. 21.2.
Если подготовлена программа, в которой функция main() принимает два аргумента, то из командной строки можно передать, например, некоторые исполняемые файлы, такие как notepad. exe, winword. exe, чтобы можно было открыть "Блокнот" или документ "Word". Практическая часть Пример 1. Напишите программу с аргументами командной строки, в которую передайте строки "Hello, world", "D:\forma. exe", "MS Visual Studio 2010", "D:\knapsack. exe", "команда cmd", где исполняемый файл "forma. exe" обеспечивает форматированный вывод случайных чисел, распределенных по нормальному закону, исполняемый файл "E:\knapsack. exe" решает задачу о рюкзаке (см. Тему 18). Аргументы командной строки определите в среде MS Visual Studio 2010. Прежде чем реализовать программный код решения примера, приведем программный код для исполняемого файла "forma. exe". Программный код форматированного вывода случайных чисел: // Файл main. c программы forma (forma. exe) #include <stdio. h> #include <conio. h> #include <stdlib. h> #include <malloc. h> #include <time. h> #include <locale. h> #include <string. h> #include <math. h> #include <float. h> #include <limits. h> int main (void) { int i, j; double nd, md, fd, posd; double factor = 1.23; int n, m, f, pos; // row, column, floor point, distance int in = 0; double **A = NULL; double S = 0.0; double V1, V2, R1, R2; double tmax, tmin; char *smax, *smin; FILE *fid; errno_t err; time_t t; setlocale(LC_ALL, "rus"); setlocale(LC_NUMERIC, "uk"); srand((unsigned)time(&t)); // рандомизация случайных чисел printf("\n Введите количество строк двумерного массива: "); in = scanf_s("%lf", &nd); if (in!= 1) { printf("\n Ошибка ввода. Нажмите любую клавишу: "); _getch(); exit(1); } if ( nd < 1.0 || floor(nd) != nd ) { printf("\n Ошибка! Количество строк массива должно быть\ натуральным числом.\n \ Нажмите любуюу клавишу: "); _getch(); exit(1); } if (nd > (double)(INT_MAX/10000) ) { printf("\n Слишком большое число. Нажмите любую клавишу: "); _getch(); exit(1); } printf(" Введите количество столбцов двумерного массива: "); in = scanf_s("%lf", &md); if (in!= 1) { printf("\n Ошибка ввода. Нажмите любую клавишу: "); _ getch(); exit(1); } if ( md < 1.0 || floor(md) != md ) { printf("\n Ошибка! Количество столбцов массива должно быть натуральным числом.\n \ Нажмите любуюу клавишу: "); _getch(); exit(1); } if (md > (double)(INT_MAX/10000) ) { printf("\n Слишком большое число. Нажмите любую клавишу: "); _getch(); exit(1); } printf(" Введите произвольное действительное число: "); in = scanf_s("%lf", &factor); if (in!= 1) { printf("\n Ошибка ввода. Нажмите любую клавишу: "); _getch(); exit(1); } if ( factor > (double)(INT_MAX/1000) || factor < (double)(INT_MIN/1000)) { printf("\n Слишком большое число. Нажмите любую клавишу: "); _getch(); exit(1); } if ( factor < (10000.0*DBL_EPSILON ) && factor > -(10000.0*DBL_EPSILON ) ) { printf("\n Слишком малое число. Нажмите любую клавишу: "); _getch(); exit(1); } printf(" Введите количество цифр после десятичной точки: "); in = scanf_s("%lf", &fd); if (in!= 1) { printf("\n Ошибка ввода. Нажмите любую клавишу: "); _getch(); exit(1); } if ( fd < 0.0 || fd > 14){ printf("\n Количество цифр должно лежать в диапазоне [0; 14].\ \n Нажмите любую клавишу: "); _getch(); exit(1); } if ( floor(fd) != fd ) { printf("\n Количество цифр должно быть целым числом \ в диапазоне [0; 14].\n Нажмите любую клавишу: "); _getch(); exit(1); } printf(" Введите минимальное количество позиций \ между столбцами массива: "); in = scanf_s("%lf", &posd); if (in!= 1) { printf("\n Ошибка ввода. Нажмите любую клавишу: "); _getch(); exit(1); } if ( floor(posd) != posd ) { printf("\n Количество позиций должно быть целым числом \ в диапазоне [1; 10].\n Нажмите любую клавишу: "); _getch(); exit(1); } if ( posd < 0.0 || posd > 10.0) { printf("\n Количество позиций должно лежать в диапазоне \ [1; 10].\n Нажмите любую клавишу: "); _getch(); exit(1); } f = (int)fd; pos = (int)posd; n = (int)nd; m = (int)md; A = (double **)malloc(n*sizeof(double *)); for (i = 0; i < n; i++) A[i] = (double *)malloc(m*sizeof(double)); // Заполнение матрицы по нормальному закону // метод Марсальи-Брея S = 1.0; for (i = 0; i < n; i++) { for (j = 0; j < m; j++) { while (S >= 1.0) { R1 = (double) rand()/RAND_MAX; R2 = (double) rand()/RAND_MAX; V1 = 2.0*R; V2 = 2.0*R; S = (V1*V1 + V2*V2); } A[i][j] = factor * V1 * sqrt(-2.0*log(S)/S); S = 1.0; } } tmax = tmin = A[0][0]; for (i = 0; i < n; i++) for (j = 0; j < m; j++) if (tmax < A[i][j]) tmax = A[i][j]; for (i = 0; i < n; i++) for (j = 0; j < m; j++) if (tmin > A[i][j]) tmin = A[i][j]; smax = (char *)malloc((pos+f+(int)INT_MAX/10000 )*sizeof(char)); sprintf_s(smax, (pos+f+(int)INT_MAX/10,"%0.*f",f, tmax); smin = (char *)malloc((pos+f+(int)INT_MAX/10000 )*sizeof(char)); sprintf_s(smin, (pos+f+(int)INT_MAX/10,"%0.*f",f, tmin); // int n, m, f, pos; // row, column, floor point, distance in = (strlen(smin) > strlen(smax)? strlen(smin): strlen(smax)); if ((in+pos)*m < 80) { puts("\n Пример форматированного вывода числовой матрицы:"); for (i = 0; i < n; i++) { puts(""); for (j = 0; j < m; j++) printf("%*.*f", in + pos, f, A[i][j]); } puts(""); } else if ((in+pos)*m < 1000) { if ( (err = fopen_s(&fid, "format. txt", "w")) != 0 ) { printf("\n Файл для записи \"format. txt\" не может \ быть открыт.\n Нажмите любую клавишу: "); _getch(); exit(1); } fprintf(fid, "\r\n\t Пример форматированного вывода \ числовой матрицы: \r\n"); fprintf(fid, "\r\n Матрица размера %dx%d из нормально \ распределенных случайных чисел,\r\n \ умноженных на число %g. Количество цифр после \ десятичной точки: %d\r\n \ Количество позиций между числами: %d\r\n",n, m,factor, f,pos); for (i = 0; i < n; i++) { fprintf(fid, "\r\n"); for (j = 0; j < m; j++) fprintf(fid, "%*.*f", in + pos, f, A[i][j]); } fprintf(fid, "\r\n"); printf("\n Результат смотрите в файле \"format. txt\"\n" ); fclose(fid); } else { printf("\n Результат не может быть форматированно выведен\n \ в текстовый документ или на консоль для заданного\n \ количества столбцов, величины поля между числами\n \ и количеством знаков после десятичной точки.\n"); printf("\n Нажмите любую клавишу: "); _getch(); exit(1); } printf("\n\n... Нажмите любую клавишу: "); _getch(); return 0; } Исполняемый файл этой программы разместим на одном из жестких дисков, например, на диске D, т. е. D:\forma. exe. Также и файл "knapsack. exe" разместим на диске D, т. е. D:\knapsack. exe. Для запуска исполняемых приложений используем функцию _spawnl(), для которой подключим заголовочный файл <process. h>. Основной программный код решения примера: #include <stdio. h> #include <conio. h> #include <locale. h> #include <process. h> #include <ctype. h> #include <string. h> int main (int argc, char *argv[]) { int count, p, i; int j; int N[20]; // массив для индексов исполняемых приложений count = p = i = 0; // На случай, когда командная строка содержит русские шрифты setlocale(LC_ALL, "rus"); printf("\n Общее количество аргументов: %d\n\n", argc); if (argc == 1) printf(" Имя аргумента и путь к нему:\n%4d) %s\n", count+1, argv[count]); else { for (count = 0; count < argc; count++) { printf(" %3d) %s\n", count+1, argv[count]); if ( (strstr(argv[count], ".exe")) != NULL) N[i++] = count; } } if (i > 1) { if (i == 2) printf("\n Для запуска исполняемого приложения \n \ можно ввести число "); else printf("\n Для запуска исполняемого приложения \n \ можно ввести числа "); for (j = 1; j < i; j++) { if (j < i-1) printf("\"%d\", ", N[j]+1); else printf("\"%d\": ", N[j]+1); } scanf_s("%d", &p); _spawnl(_P_WAIT, argv[p-1], argv[p-1], NULL); } else printf("\n Исполняемых приложений нет.\n"); printf("\n Завершение проекта tcmd... Press any key: "); _getch(); return 0; } В программе используется функция strstr() для поиска заданной подстроки (.ехе) в заданной строке символов. Для этой функции подключен заголовочный файл <string. h>. В общем случае исполняемые файлы могут быть заданы без расширения. ехе. Можно также имена исполняемых файлов передавать в функцию как строки, определенные в самой программе без использования командной строки. Пример выполнения программы показан на рис. 21.3.
Задание 1 Внесите изменения в программу, чтобы исполняемые файлы выполнялись без указания расширения. ехе. В программу внесите изменения для определения номера (номеров) индексов первого символа подстроки (подстрок) ".ехе". В командную строку (с помощью закладки Alt+F7) введите номер компьютера, на котором выполняется лабораторная работа. Проверьте работу программы. Проверьте работоспособность программы, когда в качестве второго аргумента функции main() будет использоваться char **argv. Внесите в программу изменения для посимвольного вывода аргументов командной строки. В программе предусмотрите защиту от неправильного ввода данных с клавиатуры.Пример 2. Напишите программу с аргументами командной строки, которые вводятся с помощью интерпретатора cmd. В качестве аргументов примите исполняемый файл forma. exe (см. предыдущий пример), notepad. exe, winword. exe. Файлы notepad. exe, winword. exe расположены в директориях "C:\WINDOWS\system32" и "C:\Program Files\Microsoft Office\OFFICE12" (последний путь зависит от версии Microsoft Office). Расположим указанные исполняемые файлы на диске С:. Программный код решения примера 2 можно оставить таким же, что и для примера 1. Начало выполнения программы показано на рис. 21.4.
В соответствии с введенным номером аргумента командной строки открывается "Блокнот", который показан на рис. 21.5.
После закрытия блокнота (Безымянный - Блокнот) произойдет завершение программы tcmd. Задание 2 Проверьте работу программы при расположении указанных исполнимых приложений на разных жестких дисках. Проверьте работу программы по всем номерам исполняемых приложений. В программу внесите операцию чтения текстового файла (из блокнота, который откроется после выбора notepad. exe), в котором записать номер компьютера, за которым выполняется лабораторная работа, специальность обучения (например, АСОИУ) и номер учебной группы В качестве аргумента командной строки введите команду ping. exe (путь к ней: C:\WINDOWS\system32). Скриншот полученного результата вставьте в отчет лабораторной работы. В качестве аргумента командной строки введите команду qprocess. exe (путь к ней: C:\WINDOWS\system32). Скриншот полученного результата вставьте в отчет лабораторной работы.Контрольные вопросы Какой тип имеют аргументы командной строки? Какое основное назначение имеют аргументы командной строки? Каким образом разделяются аргументы командной строки? К чему приводит инкрементирование второго аргумента функции main() в программе, в которой происходит обращение к этому аргументу? Каким образом можно вставить содержимое буфера памяти (например, полный путь к команде notepad. exe или строку из текстового документа) в командную строку операционной системы Windows? | ||||||||||||||||||||||||||
| ||||||||||||||||||||||||||
Программирование на языке C в Microsoft Visual Studio 2010 | ||||||||||||||||||||||||||
| ||||||||||||||||||||||||||
| ||||||||||||||||||||||||||
| ||||||||||||||||||||||||||
Дополнительные материалы: Контрольная работа № 1: версия для печати и PDA | ||||||||||||||||||||||||||
| ||||||||||||||||||||||||||
| ||||||||||||||||||||||||||
| ||||||||||||||||||||||||||
Вычисление последовательности Фибоначчи с использованием больших чисел В языке программирования C существует большое количество разнообразных целочисленных типов данных, области определения которых охватывают числа различных диапазонов. При решении задачи программист должен выбрать правильные типы данных для числовых переменных, исходя из природы соответствующих данных, а также требований к занимаемой программой и ее данными памятью и желаемым быстродействием. Тем не менее, диапазоны встроенных целочисленных типов данных языка C ограничены, что не позволяет выполнять вычисления над достаточно большими числами (например, с разрядностью более 20 десятичных цифр). Такое ограничение оказывается серьезным препятствием при решении задач, природа которых требует выполнения операций над большими числами, например задач, требующих вычисления элементов быстрорастущих последовательностей. В частности, использование типа int языка C для вычисления элементов последовательности Фибоначчи позволяет получить только 46 первых чисел, после его происходит целочисленное переполнение, и получение следующих элементов становится невозможным. Такая проблема ограниченности диапазонов целочисленных типов данных решается при помощи так называемой "длинной арифметики". Длинная арифметика – в вычислительной технике операции над числами, разрядность которых превышает длину машинного слова данной вычислительной машины. На практике длинная арифметика применяется в следующих случаях:
Рассмотрим, каким образом можно хранить длинные целые числа в памяти компьютера и как выполнять над ними действия. Обычно длинное число представляют в виде массива, элементы которого хранят цифры длинного числа, и отдельно дополнительно сохранят длину числа, то есть количество значимых цифр. При хранении длинного числа удобнее перейти от десятичной системы счисления к системе счисления с большим основанием, так как это позволит лучше использовать пространство оперативной памяти, используемой под массив. Количество значимых цифр будем хранить в первом элементе массива (элементе с индексом 0). Цифры числа будем хранить в обратном порядке, то есть младшая цифра будет храниться в элементе массива с меньшим индексом. Сделаем следующие объявления: /// максимальная длина числа - 1000 знаков #define NUMMAX 1000 /// основание системы счисления длинных чисел - 10^9 #define NUMBASE /// получениедлины числа #define NUMLEN(n) ((n)[0]) /// определение типа длинных чисел typedef int number_t[NUMMAX + 1]; Итак, мы определили тип данных длинных чисел – number_t. С его помощью можно хранить и обрабатывать числа длиной до 9000 десятичных знаков. Основание системы счисления выбрано равным 109, это позволяет хранить в одном элементе массива до 9 десятичных знаков. Рассмотрим пример хранения двух чисел: 1 и . Число 1 меньше выбранного основания системы счисления, поэтому оно будет представлено одной значимой цифрой и для его хранения будет достаточно 1 элемента массива. Схема вышеприведенного описания может быть следующей:
Число больше выбранного основания системы счисления, поэтому оно будет представлено двумя значимыми цифрами и для его хранения потребуется 2 элемента массива. Схема приведенного описания может быть следующей:
Создадим три вспомогательные функции, выполняющие установку значения длинного числа. Функция numzero() устанавливает длинное число равным нулю. Программный код функции: /// сброс длинного числа в ноль void numzero (number_t lhs) { lhs[0] = 1; lhs[1] = 0; } Функция numassgns() присваивает длинному числу короткое значение из диапазона 0..232–1. Программный код функции: /// присваивание короткого числа длинному числу void numassgns (number_t lhs, unsigned rhs) { lhs[0] = 0; while (rhs) { lhs[++lhs[0]] = rhs % NUMBASE; rhs /= NUMBASE; } } Функция numassgn() присваивает одному длинному числу значение другого длинного числа. Программный код функции: /// присваивание длинных чисел void numassgn (number_t lhs, const number_t rhs) { int i; // переписываем длину результирующего числа lhs[0] = rhs[0]; // копируем цифры for (i = 1; i <= NUMLEN (rhs); ++i) lhs[i] = rhs[i]; } Для вывода длинного числа на экран создадим функцию numprint(). Эта функция сначала проверяет длину числа, и если она равна 0, то выводит число 0, в противном случае она печатает цифры числа на экране проходя по массиву в обратном направлении, от индексов с большими значениями до индексов с меньшими значениями, так как цифры числа хранятся в обратном порядке. Программный код функции: /// печать длинного числа void numprint (const number_t lhs) { int i; printf ("%d", NUMLEN (lhs) ? lhs[NUMLEN (lhs)] : 0); for (i = NUMLEN(lhs) - 1; i > 0; --i) printf ("%09d", lhs[i]); } Операция сложения длинных чисел реализует обычное сложение чисел столбиком. Вспомним, как выполняется такая операция. Пусть надо сложить два числа 12345 и 678. Записываем эти два числа в столбик таким образом, чтобы младшие разряды числа оказались друг под другом. После этого по таблице сложения складываем независимо разряды друг с другом. Если результат превосходит 9, то запоминаем 1, переносим ее в старший разряд, а в текущем разряде записываем младший разряд от результата сложения. И так продолжается до тех пор, пока все разряды не будут учтены. Обратите внимание, что если длина чисел разная, то в старших разрядах более длинного числа сложение производится только с "запомненной" 1 переноса. Схема вычислений может быть следующей:
Сложение чисел выполняется функцией numadd(). Функция принимает два числа – слагаемых и записывает результат в параметр с именем res. Функция выбирает из двух слагаемых более короткое и сначала складывает разряды двух чисел, затем, когда все разряды более короткого числа будут учтены, добавляет оставшиеся разряды более длинного числа с учетом переноса, признак которого хранится в переменной c. Так как цифры числа хранятся в обратном порядке, то сложение осуществляется в порядке возрастания индексов массива. Программный код функции: /// сложение длинных чисел void numadd (number_t res, const number_t lhs, const number_t rhs) { int i = 0; // флаг переноса int c = 0; // число с минимальной длинной const int *sn = NUMLEN (lhs) < NUMLEN (rhs) ? lhs : rhs; // число с максимальной длиной const int *ln = sn == lhs? rhs : lhs; // складываем два числа while (i < NUMLEN (sn)) { ++i; res[i] = c + sn[i] + ln[i]; c = res[i] > NUMBASE? 1 : 0; if (c) res[i] -= NUMBASE; } // добавляем остаток от более длинного числа и перенос while (i < NUMLEN (ln)) { ++i; res[i] = c + ln[i]; c = res[i] > NUMBASE? 1 : 0; if (c) res[i] -= NUMBASE; } // учитываем последний перенос if (c) res[++i] = c; // сохраняем длину числа res[0] = i; } Рассмотрим пример использования арифметики длинных чисел. Функция main() создает три переменные для хранения длинных чисел, инициализирует из значениями, и выполняет операцию сложения, после чего печатает результат на экране. Программный код главной функции: int main (int argc, char* argv[]) { int i; number_t a, b, c; numassgns (a, ); numassgns (b, 1); numadd (c, a, b); numprint (c); printf("\n\n... Press any key: "); _getch(); return 0; } Задание: Создайте функцию numtoa (), выполняющую преобразование длинного числа в строку. Функция должна иметь следующий прототип:2. /// перевод длинного числа в строку 3. void numtoa (const number_t num, char *str); Создайте функцию atonum (), выполняющую преобразование строки в длинное число. Функция должна иметь следующий прототип:5. /// перевод строки в длинное число 6. void atonum (const char *str, number_t num); Разработайте программу, выполняющую вычисление 500 первых чисел последовательности Фибоначчи. Элементы последовательности Фибоначчи вычисляются по следующему рекуррентному соотношению: | ||||||||||||||||||||||||||
| ||||||||||||||||||||||||||
Программирование на языке C в Microsoft Visual Studio 2010 | ||||||||||||||||||||||||||
| ||||||||||||||||||||||||||
| ||||||||||||||||||||||||||
| ||||||||||||||||||||||||||
Дополнительные материалы: Контрольная работа №2: версия для печати и PDA | ||||||||||||||||||||||||||
| ||||||||||||||||||||||||||
| ||||||||||||||||||||||||||
| ||||||||||||||||||||||||||
Покупки в супермаркете Сканер в кассе супермаркета выдает последовательность штрих-кодов для товаров в корзине покупок, например: 1234, 4719, 3814, 1112, 1111, 1111, 1234, которая должна быть преобразована в чек следующего вида: C supermarket Dry Sherry, 1lt.......... (x2) $108.20 Fish Fingers............. (x1) $12.11 Orange Jelly............. (x1) $5.61 Giant Hula Hoops......... (x1) $13.31 Hula Hoops............... (x2) $4.22 TOTAL.......................... $143.45 Решение начнем с анализа данных. Для представления номенклатурной единицы супермаркета создадим структуру, хранящую данные о штрих-коде товара, его наименовании и цене. Цену будем хранить в центах. Определение структуры: /// Информация о товаре магазина struct Item { /// штрих-код товара int m_barCode; /// наименование товара, не более 85 символов char m_name[86]; /// цена 1 единицы товара в центах int m_price; }; База данных (БД) товаров в простейшем случае – массив, который хранит структуры товара и количество номенклатурных позиций супермаркета. Опишем БД товаров в виде следующей структуры: /// База данных товаров struct ItemDatabase { /// количество товаров в БД int m_count; /// массив товаров struct Item *m_items; }; Для поиска товара по его штрих-коду в БД создадим функцию FindItem(), которая будет принимать два аргумента: указатель на БД товаров и штрих-код товара, информацию по которому необходимо извлечь из БД: /// Поиск товара в БД по штрих-коду struct Item * FindItem (const struct ItemDatabase *database, int barCode) { int i; assert (database!= NULL); assert (database->m_count > 0); for (i = 0; i < database->m_count; ++i) if (database->m_items[i].m_barCode == barCode) return database->m_items + i; return database->m_items; } Функция линейным поиском проходит по БД товаров, и если ей удается найти товар с соответствующим штрих-кодом, возвращает указатель на структуру с информацией о нем. Если товара с таким штрих-кодом нет, функция возвращает указатель на первый товар в БД. Для представления товара в чеке введем дополнительную структуру, которая будет хранить пару "товар – количество товара в чеке": /// Строка счета struct BillItem { /// товар const struct Item *m_item; /// количество в счете int m_quantity; }; // Чек – это массив структур BillItem. /// Счет struct Bill { /// количество товаров в счете int m_count; /// массив товаров в счете struct BillItem *m_items; }; Создадим две вспомогательные функции, которые будут выполнять управление памятью для структуры чека. Функция AllocBill() выделяет память под структуру чека и резервирует память под массив товаров; функция FreeBill() освобождает всю память, связанную с чеком: /// Выделяет память под счет struct Bill * AllocBill (int itemsCount) { struct Bill *result; result = malloc (sizeof (struct Bill)); if (!result) return NULL; // выделение памяти было неуспешным result->m_count = 0; result->m_items = malloc (itemsCount * sizeof (struct BillItem)); if (!result->m_items && itemsCount > 0) { free (result); return NULL; } return result; } /// Освобождает память, связанную со структурой счете void FreeBill (struct Bill *bill) { assert (bill!= NULL); if (bill->m_items) free (bill->m_items); free (bill); } Для печати чека на кране создадим функцию PrintBill(). На входе она принимает указатель на печатаемый чек и выводит его на экран с соответствующим форматированием: /// Печатает счет на экран void PrintBill (const struct Bill *bill) { int i; int totalBill = 0; assert (bill!= NULL); // печатаем заголовок printf ("C supermarket\n\n"); // печатаем список товаров for (i = 0; i < bill->m_count; ++i) { int totalLine = bill->m_items[i].m_item->m_price*bill->m_items[i].m_quantity; totalBill += totalLine; printf ("%s (x%d) $%d.%d\n", bill->m_items[i].m_item->m_name, bill->m_items[i].m_quantity, totalLine / 100, totalLine % 100 ); } // печатаем строку итого printf ("\nTOTAL $%d.%d\n", totalBill / 100, totalBill % 100); } Центральной функцией является функция ProduceBill(), которая по массиву штрих-кодов создает структуру счета. Функция сначала выделяет память под результат при помощи AllocBill(), затем проходит по списку переданных штрих-кодов, ищет информацию о соответствующем товаре в БД и включает ее в счет. Если в счете уже имеется такой товар, то функция просто увеличивает его количество. Если такого товара еще нет, функция включает в счет новую строку: /// Создает счет по списку товаров struct Bill * ProduceBill (const struct ItemDatabase *database, const int *barCodes, int count) { int i; struct Bill *result; assert (database!= NULL); assert (barCodes!= NULL); result = AllocBill (count); if (!result) return NULL; // формируем счет for (i = 0; i < count; ++i) { // выполняем поиск элемента в счете, может он был добавлен ранее int index = IndexOfBillItem (result, barCodes[i]); if (index == -1) { // не нашли, добаляем новый элемент result->m_items[result->m_count].m_item = FindItem (database, barCodes[i]); result->m_items[result->m_count].m_quantity = 1; ++result->m_count; } else { // нашли, тогда увеличиваем количество ++result->m_items[index].m_quantity; } } return result; } Для проверки, есть ли уже товар с некоторым штрих-кодом в счете, функция ProduceBill() использует специальную вспомогательную функцию IndexOfBillItem(). Функция возвращает индекс товара в счете либо число –1, если такого товара в счете нет. Программный код вспомогательной функции IndexOfBillItem(): /// Поиск товара в счете int IndexOfBillItem (const struct Bill *bill, int barCode) { int i; assert (bill!= NULL); for (i = 0; i < bill->m_count; ++i) if (bill->m_items[i].m_item->m_barCode == barCode) return i; return -1; } Базу данных товаров определим в виде константы с помощью спецификатора static: /// База данных товаров в супермаркете static struct Item g_databaseItems[] = { { 0, "Unknown item", 0 }, { 4719, "Fish Fingers", 1211 }, { 5643, "Nappies", 1010 }, { 3814, "Orange Jelly", 561 }, { 1111, "Hula Hoops", 211 }, { 1112, "Giant Hula Hoops", 1331 }, { 1234, "Dry Sherry, 1lt", 5401 } }; const struct ItemDatabase g_database = { sizeof (g_databaseItems) / sizeof (g_databaseItems[0]), g_databaseItems }; Функция main() выполняет создание и печать счета: int main (int argc, char* argv[]) { int codes[] = { 1234, 4719, 3814, 1112, 1111, 1111, 1234 }; struct Bill *bill = ProduceBill (&g_database, codes, sizeof (codes) / sizeof (codes[0])); if (!bill) { printf ("Error: out of memory while creating a bill"); return -1; } PrintBill (bill); FreeBill (bill); return 0; } Задание Произведите сборку программы и проведите ее компиляцию. Форматирование счета. Измените функцию PrintBill(), чтобы она печатала счет в соответствии с форматированием, приведенным в условии. Ширина строки счета – 40 символов. Отбрасывание неизвестных товаров. Измените функцию ProduceBill() таким образом, чтобы товары, не найденные в БД, не включались в счет. Вычисление скидки. За каждые две купленные бутылки шерри (код 1234) супермаркет дает скидку $5.00 с суммы счета. Добавьте вычисление скидки по счету. Результирующий счет должен выглядеть следующим образом:5. C supermarket 6. 7. Dry Sherry, 1lt.......... (x2) $108.20 8. Fish Fingers............. (x1) $12.11 9. Orange Jelly............. (x1) $5.61 10. Giant Hula Hoops......... (x1) $13.31 11. Hula Hoops............... (x2) $4.22 12. 13. Discount......................... $5.00 14. 15. TOTAL.......................... $138.27 Оптимизация поиска товара в БД. Функция FindItem() имеет сложность O(N). Измените структуру БД или тело функции FindItem() таким образом, чтобы уменьшить алгоритмическую сложность поиска. Загрузка БД из файла. Разработайте функции, выполняющие загрузку списка товаров супермаркета из CSV-файла. CSV-файлом (или файлом значений, разделенных запятыми, comma-separated values) называется текстовый файл, в котором содержаться записи, состоящие из нескольких полей. При этом каждая новая строка соответствует одной записи. Поля одной записи разделяются запятыми. Если значение поля не содержит запятых, то оно записывается непосредственно. Если значение содержит запятые, то оно заключается в двойные кавычки (").Если в такой последовательности содержится двойная кавычка, она удваивается. Имеется разновидность формата, когда все строковые значения заключаются в кавычки. Пример БД товаров в CSV-файле:18. 4719,"Fish Fingers",1211 19. 5643,"Nappies",1010 20. 3814,"Orange Jelly",561 21. 1111,"Hula Hoops",211 22. 1112,"Giant Hula Hoops",1331 23. 1234,"Dry Sherry, 1lt",5401 Редактирование БД. Добавьте функции для добавления/удаления товаров в БД. Функция AddItem() должна добавлять описание товара в БД. При этом если товар с таким штрих-кодом уже существует в БД, он должен замещаться новым. Функция RemoveItem() должна удалять товар по его штрих-коду из БД. Анализ продаж. Разработайте функцию TotalSales(), которая принимает на вход массив чеков и печатает на экране таблицу проданных товаров по всем чекам. В таблице должна присутствовать информация о названии товара, проданном количестве, сумме (возможно, с учетом скидки по соответствующим позициям). Разработайте функцию AnalyzeSales(), которая принимает на вход массив чеков и печатает на экране таблицу пар товаров, которые чаще всего покупают вместе. Пара товаров должна включаться в таблицу пар, если она присутствует более чем в одном чеке. | ||||||||||||||||||||||||||
|
Программирование на языке C в Microsoft Visual Studio 2010 |
|
|
|
Дополнительные материалы: Управление конфигурациями проекта в Visual Studio 2010: версия для печати и PDA |
|
|
|
Любой проект в Visual Studio 2010 включает несколько самостоятельных конфигураций для компиляции разных версий программы. Конфигурацией называется набор параметром компилятора, компоновщика и библиотекаря, используемый при построении проекта. По умолчанию, при создании проекта, среда Visual Studio 2010 автоматически включает в него две конфигурации: Debug (отладочная) и Release (финальная). Как следует из их названий, отладочная конфигурация используется в процессе написания программы, ее тестовых запусков для обнаружения и исправления ошибок; в то время как финальная конфигурация используется для построения конечной версии продукта, передаваемого заказчику для промышленного использования. При создании проекта настройки отладочной (Debug) и финальной (Release) конфигураций устанавливаются в значения по умолчанию. С этими настройками выполняются следующие действия:
Переключение между конфигурациями можно осуществлять из панели инструментов или при помощи окна Configuration Manager (менеджер конфигураций). Для быстрого переключения конфигурации, используемой для компиляции проекта, используется стандартная панель инструментов (рис. 24.1).
Программист может в любой момент изменить настройки конфигурации проекта или, при необходимости, создать новую конфигурацию. Эти действия выполняются в окне свойств проекта. Настройки свойств проекта применяются к текущей выбранной конфигурации. Можно указать одну из созданных конфигураций проекта, или выбрать All configurations, в этом случае настройки будут применены ко всем конфигурациям одновременно. Рассмотрим основные отличия в настройках проекта для отладочной и финальной конфигураций. На рис. 24.2 изображено окно свойств проекта со страницей настроек оптимизации (Optimization) для отладочной конфигурации проекта. Видно, что для отладочной конфигурации оптимизация генерируемого машинного кода выключена (Disabled).
Для финальной версии проекта по умолчанию включена оптимизация по скорости выполнения программы (Optimize Speed). На рис. 24.3 показана страница с выбранными настройками финальной конфигурации.
Кроме того, можно также выбрать следующие параметры оптимизации – генерировать максимально компактный код (Minimize Size) и полная оптимизация (Full Optimization), которая включает максимальные настройки оптимизации. На рис. 24.4 показан список свойств закладки Optimization.
На странице свойств генерации кода (Code Generation) можно указать версию стандартной библиотеки языка C, которая будет использована при компиляции и компоновке проекта – настройка Runtime Library. Для отладочной конфигурации по умолчанию используется настройка Multithreaded Debug DLL (многопоточная отладочная версия стандартной библиотеки, размещенной в динамически загружаемом модуле DLL). Эта версия библиотеки содержит отладочную информацию. Она также поддерживает дополнительные проверки времени выполнения, что позволяет, с одной стороны выполнять функции стандартной библиотеки под управлением отладчика, а с другой стороны – обнаруживать на раннем этапе трудно обнаруживаемые проблемы, такие как выход за границы массивов, неправильную работу с динамически распределяемой памятью и некоторые другие. Из-за наличия этих дополнительных проверок отладочная версия библиотеки выполняется медленнее финальной. Для финальной версии проекта по умолчанию используется настройка Multithreaded DLL (финальная версия стандартной библиотеки без отладочной информации, размещенной в динамически загружаемом модуле DLL). Важным моментом является то, что для запуска финальной версии программы при компиляции ее с такими настройками, модуль DLL стандартной библиотеки должен присутствовать в системе. Он устанавливается либо при установки Visual Studio 2010, либо при помощи отдельного инсталляционного пакета Microsoft Visual C++ 2010 Redistributable Package (x86). Если же библиотека DLL в системе не установлена, то скомпилированная программа не будет запущена. Для исключения зависимости от отдельной DLL стандартной библиотеки значение настройки Runtime Library нужно установить в Multithreaded (многопоточная версия стандартной библиотеки). В этом случае весь необходимый функционал будет включен непосредственно в результирующий. exe файл, который может быть запущен и исполнен независимо от того, были ли установлены файлы DLL стандартной библиотеки или нет. На рис. 24.5 показана страница свойств закладки Code Generation для отладочной конфигурации.
На рис. 24.6 показана страница свойств закладки Code Generation для финальной конфигурации.
Отладочная и финальная версия программы компилируются также с различными символами препроцессора. Для отладочной версии объявляется символ препроцессора _DEBUG, а для финальной версии – символ NDEBUG. Это позволяет использовать директивы препроцессора для условной компиляции программы, включая или исключая некоторую функциональность в отладочную или финальную версию программы. Обычно это используется для включения дополнительных проверок и отладочного вывода в отладочную версию программы. Для финальной версии такие проверки и вывод не нужны, поэтому они в нее не включаются. Например, в следующем фрагменте программы значение переменной res будет выведено на экран только в отладочной версии: int a, b; int res; a = 10; b = 20; res = a + b; #ifdef _DEBUG printf ("res = %d", res); #endif На рис. 24.7 представлена страница свойств Preprocessor для отладочной конфигурации. На рис. 24.8 представлена страница свойств Preprocessor для финальной конфигурации.
В отладочной и финальной версиях также различаются форматы отладочной информации (Debug Information Format), генерируемой компилятором и сохраняемой в. pdb файле. Для отладочной версии используется Program Database for Edit and Continue, позволяющая отлаживать и даже изменять программу, если сработала точка останова. При возобновлении выполнения программы, внесенные изменения будут автоматически применены, и выполнение продолжится уже с внесенными изменениями. Эта возможность позволяет сократить время, необходимое на остановку и перекомпиляцию программы при нахождении и исправлении ошибок. В тоже время такая настройка несовместима с настройками оптимизации, поэтому может быть использована только в отладочной версии. На рис. 24.9 показана страница свойств General для отладочной конфигурации.
В финальной версии используется настройка Program Database. Она включает генерацию. pdb файла, который может быть использован при необходимости поиска ошибок в финальной версии продукта. Эта настройка никак не влияет на оптимизацию генерируемого кода, поэтому она может быть использована для финальной версии. На рис. 24.10 показана страница свойств General для финальной конфигурации.
На странице свойств Debugging (отладка) узла Linker настройка Generate Debug Info (генерировать отладочную информацию) управляет генерацией отладочной информации. Настройка Generate Program Database File (создавать файл с отладочной информаций для программы) задает имя результирующего. pdb файла с отладочной информацией. На рис. 24.11 показана страница свойств Debugging узла Linker для отладочной версии.
MS Visual Studio 2010 предоставляет удобные и гибкие механизмы настройки свойств конфигураций проектов, что позволяет программистам выполнять компиляцию и сборку своих проектов с актуальным набором настроек. |
|








































































































































































































