Документ взят из кэша поисковой машины. Адрес оригинального документа : http://geophys.geol.msu.ru/STUDY/2KURS/progstud.doc
Дата изменения: Wed Apr 10 17:36:34 2002
Дата индексирования: Mon Oct 1 20:13:28 2012
Кодировка: Windows-1251

А.А. Горбунов
Курс по программированию для студентов 2-го курса
Целью предлагаемого вашему вниманию курса является овладение перви?ными
навыками использования персонального компьютера при решении практи?еских
зада? на разли?ных этапах геофизи?еского исследования. Выражение
"использование компьютера" всегда озна?ает использование определенного
программного обеспе?ения, то есть работу с теми или иными программами в
ка?естве пользователя.
В ходе геофизи?еского исследования приходится сталкиваться с весьма
разнообразными классами зада?. В первом приближении зада?и можно разделить
на "содержательные" и "оформительские". К "содержательным" условно можно
отнести зада?и, возникающие непосредственно при проведении геофизи?еских
исследований - при планировании эксперимента, при выполнении измерений, при
камеральной обработке данных и их интерпретации. К "оформительским" столь
же условно можно отнести зада?и, возникающие при подготовке результатов
исследования к выходу в свет - оформлении от?ета или статьи. К зада?ам
обоих классов следует относиться достато?но серьезно, поскольку коне?ный
продукт геофизи?еского (и не только) исследования есть, в идеале,
опубликованный текст. Этот текст должен содержать обоснованный ответ на
вопрос, для разрешения которого предпринято исследование. Оформлять же
текст приходится в соответствии с требованиями заказ?ика, издателя,
официального стандарта или, наконец, сложившейся традиции.
Продолжая эту условную классификацию, "содержательные" зада?и можно, в
свою о?ередь, разделить на "рас?етные зада?и" и "зада?и визуализации". К
"рас?етным" будут относиться зада?и предварительной обработки данных,
зада?и интерпретации данных (обратные зада?и), зада?и моделирования (прямые
зада?и). К "зада?ам визуализации" относится создание рабо?ей (и в какой-то
мере итоговой) графики - графиков, карт и разрезов вели?ин, используемых в
исследовании - полевых данных, результатов их обработки, итоговых
параметров объекта исследования.
Выделение особого класса "оформительских" зада? связано с возросшими за
последнее время требованиями к оформлению выходной документации. Во-первых,
в связи с компьютеризацией полиграфии издатели, как правило, требуют, ?тобы
авторы представляли работы в практи?ески сверстанном (подготовленном к
размножению) виде. Во-вторых, хорошо оформленные тексты и графика повышают
конкурентоспособность вашей организации в ее деятельности, будь то поиск и
удержание заказ?иков или же закрепление авторитета в нау?ном мире. Наконец,
в-третьих, нормальные документы лег?е хранить, искать и использовать во
внутренней деятельности организации.
Так или ина?е, все эти типы зада? встре?аются при проведении любых
работ независимо от степени и уровня компьютеризации. При нали?ии
достато?ного парка компьютерной техники, оснащенной соответствующим
программным обеспе?ением зада?и всех этих типов могут решаться с
использованием компьютера. Вообще говоря, компьютеризация не является
самоцелью. Однако грамотное использование компьютера повышает эффективность
работы, то?нее - сокращает затраты труда и времени на достижение заданного
уровня результатов.
В большинстве слу?аев работа выполняется с использованием уже
имеющегося программного обеспе?ения. "Оформительские" зада?и решаются с
помощью распространенных программ офисного назна?ения: используются
текстовые процессоры уровня MS Word, графи?еские редакторы на?ального и
полупрофессионального уровня и - редко - издательские системы. При решении
зада? "содержательных" ситуация несколько иная: используются менее
распространенные математи?еские и другие специализированные программы -
электронные таблицы, статисти?еские и картографи?еские пакеты - и
специальное программное обеспе?ение ?исто геофизи?еского назна?ения -
специфи?еские программы обработки и интерпретации данных разли?ных
геофизи?еских методов. Относительно не?асто доступные программы не
соответствуют или не вполне соответствуют решаемым зада?ам, и в этом слу?ае
возникает необходимость написания новых программ с использованием тех или
иных систем программирования.
Грамотное использование компьютера подразумевает уверенное владение
имеющимся программным обеспе?ением. Это позволяет сделать рациональный
выбор программного обеспе?ения и способов его использования. Тем самым
обеспе?ивается эффективное - с наименьшими затратами в заданный срок при
требуемом ка?естве - решение поставленной зада?и. О?евидно также, ?то
необходимым условием успеха при решении зада? будет, во-первых, уровень
профессиональной квалификации геофизика и, во-вторых, общий уровень
владения компьютером в режиме пользователя, вклю?ая понимание принципов
работы компьютера.
В соответствии с изложенными принципами в этом курсе изу?аются общие
принципы устройства и работы компьютера, основные операции работы с файлами
и "самообслуживания на компьютере", основы оформления документов в
текстовом редакторе, организация рас?етов в электронных таблицах, работа с
нау?ной графикой и картами, а также программирование - в объеме
ограни?енном, но достато?ном для решения типи?ных зада? в ходе нормального
процесса исследования.

ОСНОВЫ РАБОТЫ + Win'95

В этом курсе программирование рассматривается в том объеме и в тех
формах, которые нужны для решения текущих нау?ных или производственных
зада?. Ре?ь идет о написании программ для собственных нужд или для
использования в составе рабо?ей группы. Создание программных продуктов,
предназна?енных для распространения на современном рынке, потребует,
безусловно большего объема знаний, специфи?еских для программирования как
такового. Кроме того, при создании таких продуктов решается большое
коли?ество зада?, не имеющих отношения к собственно геофизике. Так или
ина?е, подобные продукты вряд ли могут быть созданы между делом, по ходу
решения текущих вопросов; требуется выделение для этих целей времени и
средств.
Созданию программы предшествует постановка задания на геофизи?еском
(?елове?еском) языке. Формулировка задания должна содержать: вопрос, на
который следует дать ответ; данные и допущения, на основе которых ищется
ответ; способы поиска ответа на вопрос. После уяснения задания принимается
решение о методе его выполнения, то есть об использовании того или иного
имеющегося программного обеспе?ения или о написании новой программы.
Если принято решение о написании новой программы, разрабатывается
алгоритм выполнения задания. На этом этапе под алгоритмом понимается
последовательность действий, приводящая к ответу на вопрос, поставленный в
задании. Ре?ь здесь идет еще не о написании текста программы, но о явном
описании на ?елове?еском языке процесса решения зада?и геофизиком
независимо от техни?еских средств, которыми он располагает.
На следующем этапе происходит описание процесса решения на ?елове?еском
языке, но уже в терминах программирования. Определяется структура входных и
выходных данных программы, способы ввода и вывода данных, структура самой
программы, способы программирования отдельных этапов решения. Наконец,
производится запись алгоритма на выбранном языке программирования, отладка
и тестирование программы.
ЗАПИСЬ АЛГОРИТМОВ НА ЯЗЫКЕ СИ
ВВОДНЫЕ ИДЕИ
Как уже говорилось, программа - это последовательность команд, которую
в состоянии выполнить компьютер, и которая решает некоторую зада?у. Эта
последовательность команд, во-первых, о?ень детализирована и, во-вторых,
о?ень неудобна для восприятия ?еловеком. Поэтому в большинстве слу?аев
алгоритм записывается на некотором формализованном языке (языке
программирования), и уже эта запись переводится (транслируется) в
последовательность машинных команд (машинный код). Процесс трансляции
состоит, как правило, из двух этапов. На первом этапе (компиляции) в
результате работы программы- компилятора происходит перевод исходного
текста в машинный код и образуется объектный модуль (один или несколько).
На втором этапе (редакции связей) программа - редактор связей дополняет
объектные модули командами, необходимыми для связи с внешними устройствами,
и объединяет их в исполнимый модуль, который, собственно, и является
программой.
Выполнение программы состоит в последовательном выполнении ее команд,
находящихся в оперативной памяти. Команды так или ина?е изменяют данные,
также хранящиеся в оперативной памяти. В соответствии с этим программа
состоит из раздела, выделяющего память под хранение данных, и раздела,
управляющего изменением данных. То?но так же в исходном тексте программы
(записи алгоритма на языке программирования) выделяются области описания
переменных и области описания действий.
В области описания переменных вводятся символьные обозна?ения (имена)
областей памяти, выделяемых для хранения данных.
При описании действий следует иметь в виду несколько принципов.
1. Направленность алгоритма. Действия выполняются строго в том порядке,
в каком они следуют в записи, если не встре?ается одна из следующих
возможностей.
2. Возможность циклов. Некоторые действия приходится выполнять
несколько раз подряд, возможно, с закономерными изменениями от раза к разу.
3. Возможность ветвления. Иногда приходится выполнять одно действие,
если некоторое условие выполнено, и другое, если это условие не выполнено.
4. Возможность структурирования. Если некоторая последовательность
действий встре?ается в разных у?астках программы, возможно, при разных
зна?ениях переменных, то она может быть явно выписана один раз, а далее
вызываться по условному имени.


ПОНЯТИЕ ОПЕРАТОРА
Действия состоят в изменении зна?ения переменных, то есть в изменении
содержимого соответствующих областей памяти. Описание действий представляет
собой последовательность операторов.
Оператор - в первом приближении - инструкция, описывающая достато?но
элементарный шаг алгоритма, оформленная в соответствии с требованиями языка
программирования. Оператор закан?ивается символом ; и может быть записан в
несколько строк. Выделяют операторы присваивания, операторы вызова функции,
операторов цикла и ветвления. Несколько последовательных операторов, взятых
как целое в фигурные скобки, образуют составной оператор. Составные
операторы ?асто используются в циклах и при ветвлении.
Кроме описаний и операторов текст программы может содержать комментарии
- текст, игнорируемый при трансляции. Комментарий на?инается парой символов
/* и закан?ивается парой символов */. Комментарии используются для записи
развернутых пояснений к тексту программы, ?то облег?ает ?тение текста в
процессе отладки, модификации, при публикации и т. п. Экономить на
комментариях не следует.
СТРУКТУРИРОВАНИЕ И СТРУКТУРА ПРОГРАММЫ
Под структурированной программой будем понимать программу, состоящую из
ряда особым образом обособленных фрагментов (подпрограмм, в терминах Си -
функций). Функция может быть вызвана из другой функции. После выполнения
действий, описанных в вызванной функции, продолжается выполнение первой;
при таком возврате вызванная функция, как правило, порождает некоторое
зна?ение (возвращает зна?ение), которое может быть присвоено одной из
переменных вызывающей функции, или использовано в составе выражения.
Структурированные программы обладают рядом преимуществ.
1. Исходный текст структурированной программы лег?е ?итается ?еловеком,
?то удобно при отладке.
2. Использование функций делает исходный текст и саму программу более
компактной.
3. Функции могут отлаживаться раздельно, ?то упрощает (как минимум -
упорядо?ивает) процесс отладки, в том ?исле при работе в коллективе.
4. Функция, созданная для одной программы, может быть использована в
другой, ?то экономит время при написании новых программ.
5. В комплект поставки среды программирования входит набор функций,
реализующих ?асто используемые фрагменты многих алгоритмов (ввод-вывод,
математика и др.).
Так или ина?е, текст программы на языке Си всегда есть набор функций.
Из них первой выполняется функция со стандартным именем main. Эта функция
обязательно должна присутствовать в тексте и может быть единственной, то
есть содержать текст программы целиком.
ТИПЫ ВЕЛИЧИН И ОПИСАНИЕ ПЕРЕМЕННЫХ
Все переменные, используемые в программе, должны быть описаны до того,
как они используются в каком-либо операторе программы. Переменные могут
использоваться в пределах одной функции (то есть изменяться только во время
ее выполнения, быть локальными) или быть доступными для всех функций данной
программы (быть глобальными). Описания локальных переменных обы?но
располагают внутри функции до первого оператора, описания глобальных
переменных - вне всех функций перед первой.
Запись:
назв_типа имя_перем;
описывает переменную типа назв_типа с именем имя_перем. Тип переменной
определяет размер области памяти, выделяемой под эту переменную и, вообще
говоря, какие операции можно с этой переменной выполнять. Имя переменной -
это набор прописных и стро?ных букв латинского алфавита и цифр; набор
на?инается с буквы и не содержит пробелов. Чаще всего в ка?естве имени
используют слово или сокращение, отражающее физи?еский смысл
соответствующей вели?ины. Запись вида:
назв_типа имя_перем1, имя_перем2, имя_перем3;
описывает несколько переменных соответствующего типа с указанными
именами. Коли?ество переменных, вводимых подобной записью не ограни?ено.
Предусмотрено несколько стандартных типов:
сhar - символы, целые ?исла 0-256 (ASCII-коды), 1 байт;
int - целые ?исла от -32768 до 32768, 2 байта;
float - вещественные ?исла от -10е38 до 10е38 ?ерез 10е-38, 4 байта;
double - вещественные ?исла от -10е308 до 10е308 ?ерез 10е-308, 8
байт.
Данные символьного типа используются при работе с текстовой
информацией, данные целого типа - для подс?ета и нумерации, данные
вещественных типов - в рас?етах.
Кроме переменных в программе могут встре?аться константы - явная запись
конкретного ?исла или символа. Типы констант - те же ?то у переменных.
Целые константы могут быть десяти?ными (обы?ная запись десяти?ного целого
?исла), восьмери?ными (на?инаются с 0) или шестнадцатери?ными (на?инаются с
0х). Вещественная константа имеет тип double и выглядит как десяти?ная
дробь с то?кой в ка?естве разделителя целой и дробной ?асти или как ?исло в
экспоненциальной форме (в одну строку без пробелов, естественно) с символом
Е вместо 10. Символьная константа - это взятый в апострофы символ или его
ASCII-код (восьмери?ный или шестнадцатери?ный); при использовании кода
перед ним ставится символ \ (обратная косая). Набор символов, взятый в
кавы?ки, является строковой константой (строкой).
Однородные данные удобно хранить в виде массивов (занумерованных
наборов элементов, при?ем имя имеет набор в целом). Запись вида:
назв_типа имя_масс[?исло_элем];
описывает массив из элементов типа назв_типа в коли?естве ?исло_элем
(выражается целым ?ислом) с именем имя_масс. При этом можно с?итать, ?то
переменная имя_масс содержит адрес первого элемента массива (является
указателем на массив). В составе оператора запись вида:
имя_масс[целое_?исло]
озна?ает элемент массива имя_масс, отстоящий от первого на целое_?исло
элементов, то есть имя_масс[0] озна?ает 1-й элемент массива. Можно с?итать,
?то при описании массива указывается его длина (коли?ество элементов), а
при использовании элемента массива в операторе - номер элемента, при?ем
элементы нумеруются с 0. Строка трактуется как массив символов.
Запись вида:
назв_типа имя_масс[?исло_элем1][?исло_элем2];
описывает соответствующего типа массив из ?исло_элем1 массивов по
?исло_элем2 каждый (двумерный массив ?исло_элем1 х ?исло_элем2).
Длину массива назна?ают, исходя из ожидаемого коли?ества данных с
некоторым запасом, у?итывая, однако, ?то массив не должен занимать больше
64 Кбайт. Выход за границу массива (обращение к элементу массива с номером
большим, ?ем его длина) в принципе ошибкой не является, но делать этого не
следует, так как такое обращение, как правило, лишено смысла.
Запись вида:
назв_типа *имя_перем;
описывает указатель на зна?ение соответствующего типа, то есть
переменная имя_перем может содержать некоторый адрес (если описаны int a,
b; int *c;, то оператор с=&a; присвоит с зна?ение адреса а, а оператор
*c=b; запишет по адресу с [равному теперь адресу переменной а; &a -
адресное выражение; *с - обращение по указателю] зна?ение b). Подобные вещи
используются, например, при переда?е более одного зна?ения из вызванной
функции в вызвавшую. Кроме того, запись такого вида можно с?итать описанием
массива неопределенной длины, то есть в программе могут встре?аться
обращения вида имя_перем[целое_?исло]. Такие описания используют, если
планируется выделение памяти под массив в ходе работы программы
(динами?еское распределение памяти).
Запись вида:
extern опис_перем
описывает некоторую переменную как внешнюю. Это используется при
размещении текста программы в нескольких файлах для описания ранее
введенных глобальных переменных.
ВЫРАЖЕНИЯ И ОПЕРАТОР ПРИСВАИВАНИЯ
Запись вида:
имя_перем = выражение;
называется оператором присваивания. Его выполнение приводит к тому, ?то
переменная имя_перем полу?ает зна?ение выражения в правой ?асти. Типы
переменной и зна?ения выражения должны, вообще говоря, совпадать (как и
типы вели?ин, используемых в выражении). Выражения могут быть
арифмети?ескими (?аще всего), логи?ескими или выражениями отношения (эти
три типа используются в зада?ах, о которых идет ре?ь в этом курсе, ?аще
всего). В ка?естве выражения могут использоваться ?исловые константы,
отдельные переменные или вызовы функций.
Арифмети?еское выражение выглядит как запись арифмети?еских действий (+
- сложение, - - вы?итание, * - умножение, / - деление) над именами
переменных, ?ислами и вызовами функций. В выражении могут использоваться
скобки (возможно, вложенные) для указания последовательности действий.
Арифмети?еские действия выполняются над ?ислами, зна?ениями переменных и
зна?ениями, возвращаемыми функциями. Тип зна?ения определяется типом
вели?ин, входящих в выражение, в ?астности, если все операнды целые, то и
тип зна?ения будет целым (в том ?исле если в выражении есть деление;
дробная ?асть просто отбрасывается). Последовательность выполнения
определяется скобками по обы?ным правилам; в пределах скобок сперва
выполняются вызовы функций, затем в порядке следования слева направо -
умножение и деление, затем также в порядке следования слева направо -
сложение и вы?итание.
В ряде слу?аев при использовании в выражении переменных разных типов
бывает полезно перед вы?ислением выполнить преобразование типа. Запись
вида:
(назв_типа) (имя_перем)
преобразует переменную имя_перем к типу назв_типа.
Выражение отношения:
арифм_выр1 символ_сравн арифм_выр2
выглядит как неравенство. Его зна?ение имеет целый тип и, при текущих
зна?ениях арифмети?еских выражений, равно 0, если неравенство не
выполняется, или 1, если неравенство выполняется. Сравниваются при этом
зна?ения выражений после вы?исления. В ка?естве арифмети?еских выражений
могут использоваться одино?ные переменные и ?исловые константы.
Используются следующие символы сравнения, == - равно; != - не равно; < -
меньше; > - больше; <= - меньше или равно; >= - больше или равно. Выражения
отношения используются в основном в ка?естве условия выполнения того или
иного действия.
К логи?еским выражениям относятся записи операций:
логи?еского НЕ
! выр_отношения
(равно 1 если выр_отношения равно 0 и наоборот);
логи?еского ИЛИ (сложения, конъюнкции)
выр_отношения1 || выр_отношения2
(равно 1 если хотя бы одно из выражений отношения равно 1 или 0 если оба
- 0);
логи?еского И (умножения, дизъюнкции)
выр_отношения1 && выр_отношения2
(равно 1 если оба выражения отношения равны 1 или 0 если хотя бы одно -
0). Зна?ения логи?еских выражений имеют целый тип. Логи?еское выражение,
взятое в скобки, может быть использовано в ка?естве операнда другого
логи?еского выражения. Логи?еские выражения используются в основном в
ка?естве условия выполнения того или иного действия, если это условие
составное.
ОПИСАНИЕ, ОПРЕДЕЛЕНИЕ И ВЫЗОВ ФУНКЦИЙ
Функция выполняет действия, определяемые входящими в нее операторами, с
данными, переданными в нее при вызове из вызывающей функции. По окон?ании
работы функция должна передать управление и результат работы в вызвавшую
функцию. Функция состоит из заголовка, описывающего ее взаимодействие с
вызывающей функцией, и составного оператора, описывающего ее действия.
Запись вида:
назв_типа имя_функ (назв_типа1 имя_арг1, назв_типа2 имя_арг2,
назв_типа3 имя_арг3)
является заголовком функции типа назв_типа с именем имя_функ и
аргументами имя_арг1, имя_арг2, имя_арг3 соответствующих типов. Обозна?ения
аргументов в заголовке называют формальными параметрами. Коли?ество
формальных параметров функции не ограни?ено.
Тип функции определяет тип зна?ения, возвращаемого ей в вызывающую
функцию; это зна?ение может быть присвоено одной из переменных вызывающей
функции или использовано в составе выражения. Типы функций могут быть те
же, ?то и типы переменных; если функция не должна возвращать зна?ение, ей
указывается тип void. Типы всех аргументов должны быть явно указаны, даже
если несколько аргументов имеют один и тот же тип; если функция не имеет
аргументов, то в скобках ставится void. Имя функции является указателем.
Чтобы использовать функцию, в соответствующем месте вызывающей функции
должен находиться вызов этой функции. Запись вида:
имя_функ(арг1, арг2, арг3);
является вызовом функции с именем имя_функ с аргументами арг1, арг2,
арг3. В ка?естве аргументов при вызове (факти?еских параметров) могут
использоваться константы, переменные или выражения; типы факти?еских
параметров и порядок их следования при вызове должны соответствовать типам
и порядку следования формальных параметров в заголовке функции. Вызванная
функция работает со зна?ениями факти?еских параметров, подставляя их вместо
формальных. Вызов функции, возвращающей зна?ение, может быть операндом
выражения или факти?еским параметром вызова другой функции; вызов функции
типа void оформляется как отдельный оператор.
Функция завершает работу выполнением оператора возврата. Запись вида:
return выражение;
или
return;
является оператором возврата. При использовании первой формы в
вызывающую функцию передается зна?ение указанного выражения, тип которого
должен соответствовать типу функции. Вторая форма используется в функциях
типа void.
Если функция должна работать с массивом определенного типа и
определенной длины, полу?аемым из вызывающей функции, то в заголовке среди
формальных параметров должны присутствовать указатель соответствующего типа
и целая переменная, обозна?ающая длину. В вызове при этом должны
использоваться имя массива на месте указателя и целое выражение,
обозна?ающее длину массива. При этом надо иметь в виду, ?то в процессе
работы функции изменяются зна?ения элементов массива, описанного в
вызывающей функции. Поэтому, если нужно ?тобы исходный массив остался
неизменным, то в вызывающей функции и в формальных параметрах вызываемой
функции должны быть описаны два массива - исходный и результирующий, а в
вызываемой функции следует выполнять соответствующие присваивания.
Иногда бывает нужно, ?тобы результатом работы функции было не одно, а
несколько зна?ений. Чтобы полу?ить зна?ения из функции не ?ерез механизм
возврата, результирующие вели?ины должны быть описаны в заголовке функции
как указатели, а в вызове на соответствующих местах - адресные выражения. В
самой функции эти переменные должны использоваться как обращения по
указателю. Тогда после выполнения функции переменные, адресные выражения
которых использованы в ка?естве формальных параметров, полу?ат
соответствующие зна?ения.
Заголовок и следующий за ним составной оператор образуют определение
функции. Запись вида:
extern заголовок_функции;
является описанием соответствующей функции, как внешней. Определения и
описания функций не должны находиться внутри какой-либо функции. Вызов
функции возможен после ее описания или определения. Если функция описана,
то ее определение может находиться в любом месте текста программы. Если
функция не описана, то ее определение должно быть расположено до первого
вызова.
Заголовок функции main, если предполагается, ?то программа не будет
иметь параметров запуска, имеет вид:
main()
или, более строго,
int main ()
Если предполагается запуск с параметрами, то в заголовке функции main
должны быть указаны определенные стандартные аргументы. Об организации
запуска с параметрами будет сказано ниже.
ВЕТВЛЕНИЕ
Ветвление алгоритма возникает в слу?ае, если о?ередное действие должно
выполняться при выполнении определенного условия, или если при выполнении
условия должно выполняться одно действие, а при невыполнении этого условия
- другое. В записи алгоритма ветвление фиксируется оператором ветвления.
Запись вида:
if (условие) оператор
или
if (условие) оператор1 else оператор2
является оператором ветвления. Операторы, входящие в оператор
ветвления, ?асто бывают составными. В этом слу?ае оператор ветвления
выглядит как
if (условие)
{
операторы
}
или
if (условие)
{
операторы1
}
else
{
операторы2
}
В ка?естве условия используется, как правило, выражение отношения или
логи?еское выражение.
В варианте без else в слу?ае выполнения условия выполняется оператор,
стоящий после условия, а в слу?ае невыполнения условия - оператор,
следующий после оператора ветвления (то есть просто продолжается выполнение
программы). В варианте с else в слу?ае выполнения условия выполняется
оператор, стоящий после условия, а в слу?ае невыполнения условия -
оператор, следующий после else; после этого продолжается выполнение
программы. В операторах, входящих в оператор ветвления, могут
использоваться вложенные операторы ветвления. Вложенный оператор ветвления
должен целиком располагаться в пределах одного составного оператора.
Для удобства ?тения текста программы можно рекомендовать форму записи с
фигурными скобками, а каждую скобку снабжать комментарием, к ?ему она
относится.
В операторах ветвления может использоваться оператор безусловного
перехода. Запись вида:
goto метка;
является оператором безусловного перехода; метка - это любой набор
символов (как правило, осмысленное слово или сокращение). Если в программе
есть запись вида:
метка: оператор
то после выполнения оператора безусловного перехода выполняется
оператор с соответствующей меткой, а далее выполнение программы
продолжается с этого места. Метка и оператор безусловного перехода, ее
использующий, не могут находиться в разных функциях. Доказано, ?то любой
алгоритм может быть записан на языке Си без использования оператора
безусловного перехода; некоторые люди с?итают использование этого оператора
проявлением низкой квалификации.
Если алгоритм разделяется более ?ем на две ветви, можно использовать
оператор switch. Запись вида:
switch(целое_арифм_выр)
{
case константа1:
{
операторы1
break;
}
case константа2:
{
операторы2
break;
}
case константа3:
{
операторы3
break;
}
default:
{
операторы4
break;
}
}
есть оператор switch. При его выполнении вы?исляется выражение
целое_арифм_выр (вместо него может использоваться целая или символьная
переменная), а затем выполняется оператор, следующий за константой, равной
зна?ению этого выражения (если такой константы нет, выполняется оператор,
следующий за меткой default), после ?его продолжается выполнение программы.
Оператор switch эквивалентен, по сути, последовательности операторов:
if (целое_арифм_выр==константа1) {операторы1}
if (целое_арифм_выр==константа2) {операторы2}
if (целое_арифм_выр==константа3) {операторы3} else {операторы4}
Коли?ество пунктов case не ограни?ено.
ЦИКЛЫ
Циклом называется выполнение несколько раз подряд одной и той же
последовательности действий, а также сама эта последовательность действий.
Циклы возникают при необходимости выполнения однотипных операций над
элементами массива и при описании процессов рекуррентного характера, в
которых результат некоторого действия становится объектом того же действия,
выполняемого повторно.
В цикле всегда присутствует некоторая переменная вели?ина (переменная
цикла, с?ет?ик), изменяющаяся на некоторый шаг при каждом исполнении цикла
(как минимум, это номер прохода - выполнения). Кроме того, существует
условие завершения цикла, при выполнении которого продолжается дальнейшее
выполнение алгоритма; таким условием может служить выполнение цикла
определенное коли?ество раз или достижение определенного соотношения между
вели?инами, у?аствующими в процессе. Когда условие завершения выполнено,
продолжается дальнейшее выполнение алгоритма. Проверка условия должна
выполняться в зависимости от реализуемого алгоритма до выполнения текущего
шага или после него.
Перед первым исполнением цикла следует выполнить на?альные присваивания
- задать зна?ения вели?ин (как минимум - переменной цикла), с которыми цикл
выполняется первый раз.
Запись вида:
while (условие) оператор
является оператором цикла с проверкой условия перед о?ередным
выполнением (цикл while). Запись вида:
do оператор while (условие);
является оператором цикла с проверкой условия после о?ередного
выполнения (цикл do). Разница между ними состоит в том, ?то цикл while
может вообще не выполняться (если условие не выполнено перед первым
выполнением цикла), а цикл do всегда выполняется по крайней мере один раз.
Операторы, входящие в состав операторов цикла могут (и ?аще всего
бывают) составными. В ка?естве условия используется выражение отношения или
логи?еское выражение. В это выражение должна входить вели?ина, изменяемая в
ходе выполнения цикла.
О?ень ?асто цикл нужно выполнять известное коли?ество раз при изменении
переменной цикла от одного определенного зна?ения до другого по известному
закону. Для подобного слу?ая предусмотрен особый оператор цикла (цикл for).
Запись вида:
for (пер_цикла=выр1;условие для пер_цикла; пер_цикла=выр2) оператор
есть оператор цикла for. Выр1 определяет на?альное зна?ение переменной
цикла пер_цикла, условие для пер_цикла (как правило, выражение отношения с
у?астием переменной цикла) - проверяемое перед выполнением условие
завершения цикла, а выр2 определяет закон изменения переменной цикла (его
зна?ение вы?исляется каждый раз после выполнения цикла). По сути цикл for
является разновидностью цикла while.
При описании некоторых алгоритмов нужно предусмотреть возможность
досро?ного перехода к следующему выполнению цикла (до выполнения последнего
оператора цикла) или досро?ного прекращения выполнения цикла и продолжения
выполнения алгоритма. Для перехода к следующему выполнению цикла
используется оператор continue; (в цикле for вы?исляется зна?ение
переменной цикла для о?ередного шага и проверяется условие завершения
цикла), а для прекращения выполнения цикла - оператор break;. Операторы
continue и break используются, как правило, в составе оператора ветвления,
условие которого определяет необходимость выполнения этих операторов.
При использовании циклов для работы с элементами массива переменная
цикла ?асто используется для определения номера используемого элемента
массива. При этом нужно следить, ?тобы вели?ина, используемая как номер,
была всегда меньше факти?еской длины массива.
Цикл может быть вложен в другой цикл. При этом одно выполнение внешнего
цикла озна?ает полное выполнение вложенного. На?альные присваивания для
вложенного цикла ?асто нужно выполнять внутри внешнего.
Для удобства ?тения текста программы можно рекомендовать форму записи с
фигурными скобками, а каждую скобку снабжать комментарием, поясняющим к
?ему она относится.
РАЗМЕЩЕНИЕ ТЕКСТА И ПОДГОТОВКА К КОМПИЛЯЦИИ
Исходный текст программы размещается (набирается) в одном или
нескольких файлах. При размещении текста в нескольких файлах функции
определяются, а глобальные переменные описываются, один раз. Чтобы
использовать введенные в одном из файлов функцию или глобальную переменную,
в других файлах их следует описать как внешние.
Список файлов, содержащих текст программы, помещается в файл проекта.
Первым в списке должен идти файл, содержащий функцию main. В некоторых
реализациях Си файл проекта составляется обязательно, даже если текст
программы расположен целиком в одном файле. При компиляции каждый файл из
списка образует отдельный объектный модуль.
В файле, содержащем текст программы, могут использоваться директивы
препроцессора - сообщения компилятору об особенностях компиляции этого
файла.
Директива:

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

include ''имя_файла''
указывает на необходимость перед компиляцией вставить (вклю?ить) вместо
нее содержимое указанного файла. Угловые скобки используются, если
вклю?аемый файл находится в каталоге, указанном в среде программирования
как стандартный для размещения вклю?аемых файлов. Кавы?ки используются,
если вклю?аемый файл находится в том же каталоге, ?то и файл, содержащий
директиву. В стандартном каталоге находятся файлы описаний стандартных
функций, входящих в комплект поставки среды программирования. Файлы
программирующих пользователей в нем обы?но не размещаются.
Директива

define имя_конст константа
указывает на необходимость перед компиляцией заменить во всем файле
со?етание символов (набор букв и цифр, на?инающийся с буквы - обы?но, слово
или сокращение) имя_конст на указанную константу. Использование таких
"псевдопеременных" облег?ает ?тение программы и позволяет легко изменять
зна?ение констант, используемых в программе несколько раз.
Директивы препроцессора действуют в пределах файла, в котором они
находятся, то есть при необходимости должны вклю?аться во все файлы, в
которых размещен текст программы.
НЕКОТОРЫЕ ПРОЦЕДУРЫ, ИСПОЛЬЗУЮЩИЕ СТАНДАРТНЫЕ ФУНКЦИИ
МАТЕМАТИКА
В комплект поставки среды программирования входит довольно большой
набор математи?еских функций. Порядок их использования достато?но прозра?ен
и легко выясняется с использованием справо?ной системы среды. Если в
программе используются математи?еские функции, то файл с исходным текстом
должен содержать директиву
include .
ОРГАНИЗАЦИЯ ВВОДА-ВЫВОДА ИЗ ФАЙЛА/В ФАЙЛ
Подавляющее большинство программ полу?ает исходные данные из файла на
диске и записывает результаты в файл на диск. При решении многих зада?
можно этим ограни?иться, то есть не вводить в программу элементы диалога,
не имеющие отношения к содержанию зада?и.
Если программа должна работать с файлом данных (осуществлять
?тение/запись), то в на?але файла с текстом программы следует поместить
директиву
include , а в тексте должны присутствовать описание вида:
FILE *имя_перем_файла;
и оператор открытия файла вида:
имя_перем_файла = fopen(имя_файла, режим);
Открытие файла производится до на?ала работы с ним, то есть оператор
открытия должен быть использован раньше первого обращения к файлу для
?тения или записи. После открытия файл идентифицируется по своей
переменной. При необходимости можно держать открытыми несколько файлов
одновременно, каждому из них должна соответствовать отдельная переменная
типа FILE. Открываются файлы по о?ереди.
По окон?ании работы с файлом его следует закрыть, использовав оператор
закрытия файла вида:
fclose(имя_перем_файла);
Переменная имя_перем_файла может быть описана как глобальная или в
одной из функций. Открытие и закрытие файла лу?ше производить в пределах
одной функции (и обязательно так, если имя_перем_файла локально в этой
функции).
Аргумент имя_файла определяет имя открываемого файла. Этот аргумент
может быть строковой константой, или строкой (переменной, ранее описанной
как символьный массив; далее слово "строка" будет озна?ать именно это).
Зна?ение строки должно быть определено ранее (например, из параметров
запуска программы или введено в диалоге). Эта строка может содержать только
имя файла (в этом слу?ае поиск файла при открытии производится в текущем
каталоге) или файла с путем (в этом слу?ае поиск файла при открытии
производится в каталоге, определяемом путем).
Аргумент режим' указывает, какие операции предполагается выполнять с
файлом. Режим ''r'' задает использование файла для ?тения с первой позиции
(с первого символа, байта). Режим ''w'' задает использование файла для
записи с первой позиции. Режим ''а'' задает использование файла для записи
с последней позиции (дописывания в конец).
Если файл имя_файла отсутствует в каталоге, где производится поиск, то
при открытии на запись в этом каталоге создается новый пустой (нулевой
длины) файл с этим именем. При открытии на ?тение в этом слу?ае
имя_перем_файла полу?ает зна?ение NULL (пустое, неопределенное зна?ение) и
?тение невозможно; поэтому после оператора открытия файла на ?тение полезно
проверить условие имя_перем_файла==NULL и продолжать работу только если оно
не выполнено.
Если существующий файл открывается на запись с первой позиции, то
полностью и безвозвратно утра?ивается его содержимое до открытия. Это
иногда нужно, а иногда нет; при записи алгоритма это факт нужно осознавать.
При открытии на запись в конец на?альное содержимое не утра?ивается, однако
при нескольких последовательных запусках программы с одним и тем же именем
открываемого файла, этот файл содержит все, ?то выводилось в него за все
запуски; это также следует у?итывать.
Чтение файла происходит побайтно; можно говорить, ?то при ?тении
меняется (перемещается) позиция с?итывания. Перемещение происходит, вообще
говоря, всегда в одном направлении - от на?ала файла к концу. Для записи в
файл по аналогии можно говорить о позиции записи, которая, вообще говоря,
всегда находится перед самым концом файла, как бы отодвигая его от на?ала.
В зада?ах, о которых идет ре?ь в этом курсе, в ка?естве входных и
выходных используются ASCII-файлы. Входные файлы создаются, а выходные
просматриваются ?аще всего с помощью ASCII-редакторов - встроенного в NC,
MultiEdit - или Блокнота Windows. При работе с такими файлами из программы
используется так называемый форматный ввод-вывод. Далее обсуждаются способы
работы с файлами, размер которых не превышает 64 Кбайт.
Форматный ввод из открытого файла, идентифицируемого переменной
имя_перем_файла, выполняется вызовом функции вида:
fscanf(имя_перем_файла,формат,&имя_пер1,&имя_пер2,&имя_пер3);
Таким образом из файла с?итываются зна?ения переменных имя_пер1,
имя_пер2, имя_пер3, расположенные в файле друг за другом. После выполнения
с?итывания позиция с?итывания оказывается после последнего с?итанного
зна?ения. Коли?ество переменных, с?итываемых за один вызов fscanf не
ограни?ено.
Аргумент формат является строковой константой вида:
''специф_формата1специф_формата2специф_формата3''
или
''специф_формата1специф_формата2специф_формата3\n''
Здесь специф_формата1, специф_формата2, специф_формата3 озна?ают
спецификации формата. Спецификация формата соответствуют типам с?итываемых
переменных. Спецификации формата ?аще всего имеют вид:
%c - для символьных переменных;
%d - для целых переменных;
%f - для вещественных переменных типа float;
%lf - для вещественных переменных типа double;
%s - для строк (символьных массивов).
В приведенных образцах форматных строк предполагается, ?то с?итываемые
зна?ения находятся в одной строке файла и разделены пробелами. Второй
вариант форматной строки (вклю?ающий спецсимвол перевода строки \n)
используется при с?итывании всех зна?ений из некоторой строки файла; после
с?итывания по такому формату позиция с?итывания устанавливается в на?ало
строки, следующей за с?итанной. В противном слу?ае (если форматная строка
не содержит спецсимвол перевода строки \n) позиция с?итывания остается
после последнего с?итанного зна?ения в той же строке. Если разделителями
зна?ений являются не пробелы, а другие символы, их нужно добавить в
форматную строку между всеми спецификациями;
Функция fscanf имеет целый тип и возвращает коли?ество успешно
с?итанных и размещенных зна?ений, то есть вызов возможно оформить как
присваивание вида:
цел_перем = fscanf(...........);
Поскольку коли?ество зна?ений, подлежащих с?итыванию, известно для
каждого вызова fscanf, то можно предусмотреть проверку успеха каждого
выполнения этой функции.
При планировании структуры файла данных следует у?итывать изложенные
особенности функции fscanf, то есть делать структуру файла удобной для
с?итывания. Для большинства наших зада? применима такая структура: в на?але
файла одна-две строки текста (заголовок-комментарий), затем несколько строк
управляющей и вспомогательной ?исловой информации (коли?ество зна?ений и т.
п.) и затем таблица, строки которой содержат одинаковое коли?ество
зна?ений, и каждый столбец которой содержит зна?ения элементов некоторого
массива. Табли?ную ?асть удобно в этом слу?ае с?итывать в цикле, по одной
строке за проход цикла. Иногда, наоборот, зна?ения элементов массива
хо?ется расположить в строку; в этом слу?ае они с?итываются в цикле по
одному, а переход на следующую строку выполняется по завершении цикла
вызовом вида fscanf(''\n'');.
Для ввода строк из файла можно использовать функцию fgets. Запись вида:
fgets(имя_строки, длина_строки, имя_перем_файла);
есть вызов функции fgets для с?итывания строки из открытого файла,
идентифицируемого переменной имя_перем_файла. С?итанная строка помещается в
массив имя_строки, который должен быть ранее описан как символьный.
Зна?ение целой переменной или константы длина_строки определяет
предполагаемую или максимальную длину с?итываемой строки; ее лу?ше делать с
некоторым запасом (например, 80 - коли?ество символов в строке дисплея).
После выполнения fgets, если строка с?итана целиком, позиция с?итывания
переходит в на?ало следующей строки.
Форматный вывод в открытый файл, идентифицируемый переменной
имя_перем_файла, выполняется вызовом функции вида:
fprintf(имя_перем_файла,формат,имя_пер1,имя_пер2,имя_пер3);
Обозна?ения аргументов имеют тот же смысл, ?то и при обсуждении fscanf.
Все соображения по поводу формата также остаются в силе.
К этим соображениям следует добавить, ?то при выводе ?исел в
спецификацию формата обы?но добавляют коли?ество позиций, отводимых под
соответствующую вели?ину. Спецификация формата вывода целого зна?ения
приобретает вид:
%коли?_позd
а спецификация формата вывода вещественного зна?ения
%коли?_поз.коли?_поз_дробf
Целая константа коли?_поз задает в обоих слу?аях коли?ество позиций,
отводимых под зна?ение с у?етом позиции под знак (и, для вещественных, под
десяти?ную то?ку), а целая константа коли?_поз_дроб задает сколько из них
выделяется под дробную ?асть вещественного зна?ения. При выводе
вещественных зна?ений производится округление по обы?ным правилам до
указанного коли?ества десяти?ных знаков; если выделенное под дробную ?асть
коли?ество позиций избыто?но, то в младшие разряды выводятся нули.
Кроме того, при выводе в форматной строке перед, после и между
спецификациями формата можно использовать любые символы; они будут выведены
в файл в соответствующих местах вместе со зна?ениями. Это зна?ит, ?то
форматная строка для вывода может содержать короткий осмысленный текст, а
спецификации формата в этом слу?ае отме?ают в этом тексте места, в которые
будут помещены в заданном виде выводимые зна?ения.
Если форматная строка закан?ивается спецсимволом \n, следующий вывод
производится с первой позиции следующей строки, в противном слу?ае - с
последней позиции текущей строки.
Для вывода строк в файла можно использовать функцию fputs. Запись вида:
fputs(имя_строки, имя_перем_файла);
есть вызов функции fputs для записи строки в открытый файл,
идентифицируемый переменной имя_перем_файла. Выводится строка имя_строки,
который должна быть ранее описана как символьный массив и сформирована;
возможно также использование строковой константы. После выполнения fputs,
следующий вывод производится в первую позицию следующей строки.
При планировании структуры выходного файла следует иметь ввиду, ?то это
файл будет, скорее всего, использован как входной для другой программы
(пакета нау?ной графики, электронных таблиц или специальной программы).
Поэтому структура выходного файла создаваемой программы должна
соответствовать структуре входного файла программы, которая в дальнейшем
будет работать с этим файлом.
Вообще говоря, структуру входных и выходных файлов программы надо
документировать на бумаге или в отдельном текстовом файле и обращать на них
внимание тех, кто работает с программой.
НЕКОТОРЫЕ ПРИМЕРЫ И ЧАСТО ИСПОЛЬЗУЕМЫЕ ПРИЕМЫ
ВЫДЕЛЕНИЕ ЦЕЛОЙ И ДРОБНОЙ ЧАСТИ
В программе описана вещественная переменная val, которой присвоено
некоторое зна?ение. Требуется выделить целую и дробную ?асти этого
зна?ения.
В программе следует предусмотреть (описать) переменные под хранение
зна?ений целой и дробной ?асти (назовем их int_val - целая переменная под
целую ?асть, и frac_val - вещественная переменная под дробную ?асть).
Переменной int_val присвоим зна?ение val, преобразованное к целому типу.
Переменной frac_val присвоим разность зна?ения val и преобразованного к
целому типу зна?ения int_val.
Последовательность операторов:
int_val=(int)(val);
frac_val= val - (float)(int_val);

ПЕРЕСТАНОВКА ЭЛЕМЕНТОВ МАССИВА
В программе описан массив arr; его элементам присвоены некоторые
зна?ения. Также описаны целые переменные m и n, и им присвоены зна?ения,
меньшие длины массива. Требуется поменять местами элементы массива с
номерами m и n. Эта зада?а возникает при упорядо?ении массива и т. п.
Для этого следует предусмотреть (описать) промежуто?ную переменную
(назовем ее buf) того же типа, ?то и массив. Этой переменной присваивается
зна?ение одного из элементов. Сохраненному таким образом элементу
присваивается зна?ение второго элемента. Наконец, второму элементу
присвоить зна?ение промежуто?ной переменной.
Последовательность операторов:
buf=arr[m];
arr[m]=arr[n];
arr[n]=buf;
СУММА ЭЛЕМЕНТОВ МАССИВА
В программе описан вещественный массив arr; его элементам присвоены
некоторые зна?ения. Также описана целая переменная n, которой присвоено
зна?ение, равное коли?еству элементов массива. Требуется вы?ислить сумму
элементов массива. Зада?а возникает сплошь и рядом.
В программе следует предусмотреть (описать) вещественную переменную
(назовем ее sum), в которой будет храниться зна?ение суммы. Сумма
вы?исляется в цикле по всем элементам массива: переменную цикла (назовем ее
i; ее также надо предусмотреть (описать) заранее как целую) будем
использовать в ка?естве номера элемента, используемого в данном проходе
цикла; она будет изменяться от 0 до n-1 с шагом 1. Перед циклом переменной
sum присвоить зна?ение 0. На каждом проходе переменной sum присваивается
зна?ение суммы зна?ения ее самой до на?ала прохода и зна?ения элемента с
номером, равным текущему зна?ению переменной цикла.
Последовательность операторов:
sum=0.0;
for(i=0,i {
sum=sum+arr[i];
}
НАКОПЛЕННЫЕ ЧАСТОСТИ 1
В программе описан вещественный массив freq; его элементам присвоены
зна?ения ?астостей некоторой вели?ины. Также описана целая переменная n,
которой присвоено зна?ение, равное коли?еству элементов массива. Требуется
полу?ить массив накопленных ?астостей этой вели?ины. Исходный массив не
трогать.
В программе следует предусмотреть (описать) вещественный массив
(назовем его acc_freq), элементам которого будем присваивать зна?ения
накопленных ?астостей. Каждое зна?ение накопленных ?астостей определяется
как сумма ?астостей с первой по текущую вклю?ительно. Массив acc_freq
заполняется с использованием вложенных циклов. Внешний цикл организуется по
всем элементам массива acc_freq; переменную цикла (назовем ее i; ее также
надо предусмотреть (описать) заранее как целую) будем использовать в
ка?естве номера элемента массива acc_freq, используемого в данном проходе
цикла; она будет изменяться от 0 до n-1 с шагом 1. На каждом проходе
внешнего цикла элементу массива acc_freq с номером, равным зна?ению
переменной i присваивается зна?ение 0 (на?альное присваивание для
внутреннего цикла). Внутренний цикл с другой переменной цикла (назовем ее
j; ее также надо предусмотреть (описать) заранее как целую; она будет
изменяться от 0 до i-1 с шагом 1) осуществляет суммирование; сумму будем
накапливать в элементе массива acc_freq с номером равным текущему зна?ению
переменной внешнего цикла.
Последовательность операторов:
for(i=0;i {
acc_freq[i]=0.0;
for(j=0;,j {
acc_freq[i]= acc_freq[i]+ freq[j];
}
}
НАКОПЛЕННЫЕ ЧАСТОСТИ 2
В программе описан вещественный массив freq; его элементам присвоены
зна?ения ?астостей некоторой вели?ины. Также описана целая переменная n,
которой присвоено зна?ение, равное коли?еству элементов массива. Требуется
заменить зна?ения ?астостей зна?ениями накопленных ?астостей этой вели?ины.
В отли?ие от предыдущего примера, массив накопленных ?астостей уже не
нужен. Каждое их зна?ение (которое должно быть записано в некоторый
элемент) определяется как сумма зна?ений этого и всех предыдущих элементов.
Это то же самое, ?то в предыдущем примере, но с обратным порядком
суммирования (коммутативность сложения, однако). Для элемента с номером,
большим рассмотренного, повторить эту операцию уже нельзя - одно из
слагаемых будет иметь не то зна?ение. А для элемента с меньшим номером это
вполне возможно. Таким образом, зада?а решается с использованием вложенных
циклов: внешний цикл по элементам массива от последнего до первого
(переменную внешнего цикла - назовем ее i - надо предусмотреть (описать)
заранее как целую; она будет изменяться от n-1 до 0 с шагом -1) определяет,
какое зна?ение накопленной ?астности вы?исляется, а внутренний по элементам
от текущего до первого (переменную внутреннего цикла - назовем ее j - надо
предусмотреть (описать) заранее как целую; она будет изменяться от i-1 до 0
с шагом -1)выполняет суммирование.
Последовательность операторов:
for(i=n-1;i>=0;i=i-1)
{
for(j=i-1;,j>=0;j=j-1)
{
freq[i]= freq[i]+ freq[j];
}
}
ОПРЕДЕЛЕНИЕ РАНГОВ
В программе описан вещественный массив val; его элементам присвоены
зна?ения некоторой вели?ины; все зна?ения разли?ны. Также описана целая
переменная n, которой присвоено зна?ение, равное коли?еству элементов
массива. Требуется полу?ить массив рангов этой вели?ины. Рангом будем
с?итать номер элемента в упорядо?енном по возрастанию массиве (то есть
минимальное зна?ение имеет ранг 1, следующее - 2 и т. д.; равные зна?ения
имеют равные ранги).Исходный массив не трогать.
В программе следует предусмотреть (описать) целый массив (назовем его
val_rank), элементам которого будем присваивать зна?ения рангов, и
вспомогательный вещественный массив (назовем его val1), в котором будем
хранить упорядо?енные по возрастанию зна?ения элементов val. Занеся
элементы val по порядку в val1, упорядо?им val1 по возрастанию. Далее будем
по о?ереди сравнивать зна?ения каждого элемента val1 со зна?ениями всех
элементов val; когда зна?ения совпадут, элементу val_rank с номером,
равными номеру элемента val присвоим зна?ение соответствующего ранга - на
первом проходе 1, на следующем - 2, и т. д.; сравнение нужно производить
если зна?ение текущего элемента val1 не равно зна?ению предыдущего, а
соответствующему элементу val не сопоставлен ранг.
Упорядо?ение val1 производится так. Первый элемент массива принимается
за минимум, номер элемента фиксируется. Зна?ение минимума сравнивается с
зна?ениями остальных элементов, пока не найдется зна?ение, не превышающее
принятого минимума; это новое зна?ение принимается за минимум, а номер
элемента фиксируется, и сравнение продолжается до конца val1. После этого
первый элемент меняется местами с найденным минимумом. Далее все это
повторяется на?иная со 2-го элемента и т. д.
Кроме уже упомянутых, понадобится описать вещественные переменные для
минимума (назовем ее min) и для хранения зна?ения при перестановке (buf),
целые переменные для ранга (назовем ее rank) и для номера минимума (imin),
а также целые переменные циклов (i и j).
Последовательность операторов:
for (i=0;i {
val1[i]=val[i];
val_rank[i]=0;
}
for (i=0;i {
min=val1[i];
imin=i;
for (j=i;j {
if(val1[j]<=min)
{
min= val1[j];
imin=j;
}
}
val1[imin]=val1[i];
val1[i]=min;
}
rank=1;
for (i=0;i {
if( (i!=0) && (val1[i-1]!=val1[i] ) ) rank=rank+1;
if( val1[i-1]!=val1[i] )
{
for (j=0;j {
if(val[j]==val1[i]) val_rank[j]=rank;
}
}
}

ИНЫЕ ВОЗМОЖНОСТИ
РАБОТА С РАЗНОРОДНЫМИ ДАННЫМИ. СТРУКТУРЫ.
Часто данные, с которыми работает программа, представляют собой
описание некоторого объекта или нескольких объектов. При описании данных
бывает удобно "привязать" зна?ения каких-либо параметров объекта к этому
объекту. (Слово "объект" употреблено здесь в обще?елове?еском смысле и не
имеет отношения к объектно-ориентированному программированию).
В подобных ситуациях используются структуры. (Структура здесь -
название типа данных; в этом зна?ении это слово не имеет отношения к ранее
употребленным выражениям "структура файла", "структура программы",
"структура данных"). Идея состоит в том, ?то несколько переменных, возможно
- разных типов, объединяются как не?то единое под некоторым общим именем.
Запись вида:
struct имя_структуры
{
назв_типа1 имя_перем1;
назв_типа2 имя_перем2;
назв_типа3 имя_перем3;
}
описывает структуру с именем имя_структ из элементов имя_перем1,
имя_перем2, имя_перем3. Коли?ество элементов структуры не ограни?ено,
разли?ные элементы могут иметь одинаковые типы; в ка?естве элементов могут
использоваться массивы и другие структуры. Запись вида:
имя_структуры.имя_перем1
является обращением к элементу имя_перем1 структуры имя_структуры и
может использоваться в любом месте программы (с у?етом локального или
глобального характера описания), где используются обы?ные переменные и
элементы массива
Если описана структура с именем имя_структуры1, то запись вида:
struct имя_структуры1 имя_структуры2;
описывает структуру имя_структуры2 из тех же элементов, ?то и
имя_структуры1, то есть элементы имя_структуры2 имеют такие же имена и
типы, как элементы имя_структуры1. Запись вида:
struct имя_структуры1 имя_структуры2[целое_?исло];
описывает массив имя_структуры2 длины целое_?исло, элементами которого
являются структуры, состоящие из тех же элементов, ?то и имя_структуры1.
Обращение к элементу имя_перем1 структуры, входящей в массив имя_структ2
как элемент с номером n, выглядит как:
имя_структуры2[n]. имя_перем1
Пример. В то?ках, произвольно расположенных на площади, измеряются три
компоненты некоторой векторной вели?ины. Предполагается, ?то то?ек
наблюдения будет не более 1000, и координаты то?ек измерения известны.
Разрабатывается программа для обработки измерений компонент вектора на
площади. Можно говорить, ?то программа работает с то?ками наблюдений,
при?ем каждая то?ка характеризуется положением и результатами измерения,
положение - координатами (парой ?исел), а измерение в то?ке - компонентами
вектора (тройкой ?исел). При описании переменных можно ввести структуру
position для положения то?ки из двух вещественных ?исел x, y, структуру
vector для компонент из трех вещественных ?исел x, y, z, структуру для
то?ки наблюдения point из двух структур: pos типa position и value типa
vector, и наконец, массив структур рk типа point, который будет как бы
массивом то?ек наблюдения - пикетов. При дальнейшей записи текста программы
и при отладке подобное именование данных может облег?ить анализ
работоспособности программы и поиск ошибок. В записи предложенная схема
будет выглядеть:
struct position
{
float x;
float y;
}
struct vector
{
float x;
float y;
float z;
}
struct point
{
struct position pos;
struct vector value;
}
struct point pk[1000];
В дальнейшем если, скажем, нужно использовать зна?ение у-компонеты
вели?ины, измеренной на 125-м пикете, в программе используется обращение:
pk[125].value.y

ВЫВОД НА ДИСПЛЕЙ.
Вывод текста на экран используется для сообщений о ходе выполнения
программы и иногда для вывода результатов. Для вывода используются функции
printf и puts. Если предполагается их использование, файл с исходным
текстом должен содержать директиву
include .
Функция printf осуществляет форматный вывод на дисплей. Ее вызов:
printf(формат,имя_пер1,имя_пер2,имя_пер3);
похож на вызов fprintf (только за ненадобностью отсутствует ссылка на
переменную файла), и для нее справедливы все соображения, изложенные для
fprintf.
Функция puts осуществляет форматный вывод на дисплей. Ее вызов:
puts(имя_строки);
похож на вызов fputs (только за ненадобностью отсутствует ссылка на
переменную файла), и для нее справедливы все соображения, изложенные для
fputs.
Если форматная строка printf или выводимая строка puts закан?ивается
спецсимволом перевода строки \n, то следующий вывод происходит в первую
позицию следующей строки. Если же этого спецсимвола нет, следующий вывод
производится в следующую позицию той строки, куда был сделан предыдущий
вывод. Если при выводе строка упирается в правый край дисплея, она
продолжается на следующей строке дисплея. Если вывод делается в последнюю
строку экрана, то при переходе на следующую строку (вынужденном или по
спецсимволу) весь экран смещается на строку вверх; верхняя строка экрана
пропадает, а внизу появляется свободная строка, куда и производится
дальнейший (или следующий) вывод; так будет происходить и дальше.
Описанное выше поведение не всегда удобно и не всегда красиво. Среди
про?их существуют возможности о?истить экран функцией clrscr и указать,
куда сделать следующий вывод функцией gotoxy. Чтобы использовать эти
функции, следует вклю?ить в файл с исходным текстом директиву
include .
Вызов:
clrscr();
о?ищает экран и переводит то?ку вывода в левый верхний угол дисплея.
При работе с функцией gotoxy следует иметь в виду, ?то строки экрана
занумерованы сверху вниз с 1 до 25, а позиции в строке - справа налево с 1
до 80.
Вызов:
gotoxy(номер_позиции, номер_строки);
переводит то?ку вывода в положение, указанное зна?ениями целых
переменных или констант номер_позиции и номер_строки.
Следует иметь ввиду, ?то ?ем больше выводов, тем дольше исполняется
программа, ?то может быть неудобно в программах рас?етного характера.
Поэтому полезно при организации вывода стремиться к некоторому оптимуму.
Пример. Определенная в нужном месте функция work типа void выполняет
некотрые действия с элементами описанного как положено двумерного массива
val, размеры которого хранятся в переменных m и n. Работа выполняется с
использованием вложенных циклов с переменными i и j. Нужно выводить на
?истый экран (примерно в середину) индексы обрабатываемого в данный проход
элемента.
clrscr();
for(i=0;i {
for(j=0;j {
work(val[i][j]);
gotoxy(30, 10);
printf(''Обрабатывается элемент с индексами %3d, %3d '', i, j);
}
}
ИСПОЛЬЗОВАНИЕ ПАРАМЕТРОВ КОМАНДНОЙ СТРОКИ.
Для обеспе?ения возможности запуска программы с параметрами
используются аргументы функции main. Ее заголовок выглядит в этом слу?ае:
void main (int argc, char *argv[])
Аргументы полу?ают свои зна?ения при запуске программы. Вели?ина argc
озна?ает коли?ество слов - наборов символов, разделенных пробелами, в
командной строке при запуске программы. Массив строк argv содержит эти
слова. В argv[0] содержится имя запущенного исполнимого файла, а далее -
другие слова из командной строки. В зависимости от содержания этих слов
путем ветвления выбирается та или иная ветвь записанного алгоритма.
До перехода к тем или иным действиям следует проверить осмысленность
полу?енных зна?ений аргументов для программы и обеспе?ить нормальный выход
из программы, если эти аргументы бессмысленны (в том ?исле вообще
отсутствуют).
Вообще говоря, возможные параметры запуска, следует документировать -
записывать на бумаге или в отдельном текстовом файле вроде readme - и
обращать на них внимание тех, кто работает с вашей программой, а так же не
забывать самому.
Пример. Переда?а имени файла данных из командной строки.
extern job(char filename[13]);
void main (int argc, char *argv[])
{
if(argc==2)job(argv[1]);
printf(''Не задано имя файла данных\n'');
}
РАБОТА СО СТРОКАМИ
В ряде слу?аев при решении зада?и приходится предусматривать некоторые
операции с содержимым строк - ?аще всего взятие фрагмента строки, сцепление
двух строк в одну и копирование строк, а также преобразование строки из
цифр в ?исло и обратно (последнее не путать с преобразованием типов
данных).
Фрагмент строки может понадобиться, например, при выделении цифр из
строки файла данных, содержащей как цифры (зна?ение), так и буквы
(словесное пояснение к зна?ению), или при вы?ленении из имени файла
(напрмер, файла данных) его собственно имени (без расширения), ?тобы затем
приписать к нему новое расширение и использовать вновь составленное имя для
файла результатов, полу?енных по этим данным. Сцепление строк может
использоваться для формирования строки для вывода (это может иногда быть
удобнее, ?ем задавать строку формата), или, например, ?тобы приписать к
имени файла расширение перед его открытием. Копирование строк применяется,
?тобы сохранить исходный вид строки до некоторого ее преобразования, а
также - как некоторый аналог присваивания зна?ения. Наконец, преобразование
из строки в ?исло и обратно употребляется, ?тобы по вы?лененному из строки
цифровому фрагменту полу?ить зна?ение (и наоборот) или для более
экзоти?еских целей вроде нумерации.
Эти возможности осуществляются вызовом соответствующих стандартных
функций. Если предполагается использование операций взятия фрагмента,
копирования и сцепления строк, то файл с исходным текстом должен содержат
директиву
include , а для преобразования -
include .
Вызов
strcpy(cтр_куда, стр_?то);
копирует строку или строковую константу стр_?то в строку cтр_куда. Если
оба аргумента описаны как символьные массивы, то появляется две строки с
одинаковым содержимым. Длина cтр_куда должна быть не меньше, ?ем длина
стр_?то. Вообще говоря, возможны присваивания вида:
имя_строки1=имя_строки2;
или
имя_строки=строковая_константа;
В этом слу?ае, однако, операции по изменению строк, имена которых стоят
слева, изменяют и исходные строки, так как происходит просто приравнивание
адресов, а не перемещение информации.
Вызов
strcat(cтр_куда, стр_?то);
присоединяет или строковую константу стр_?то к строке cтр_куда, то есть
строка стр_?то дописывается в конец строки cтр_куда. Длина cтр_куда должна
быть не меньше суммарной длины исходных cтр_куда и стр_?то.
Строки, используемые в ка?естве аргументов функций работы со строками,
должны закан?иваться спецсимволами конца строки \0 или перевода строки \n.
Символ конца строки автомати?ески дописывается в конец строк, появляющхся
при работе функций, при выполнении программы и в конец строковых констант в
процессе компиляции. В других слу?аях это следует следует делать явным
присваиванием зна?ения элементу символьного массива.
Вызов:
имя_цел_перем=atoi(имя_строки);
преобразует строку из цифр имя_строки в соответствующее целое зна?ение и
присваивает его переменной имя_цел_перем, заранее описанной как целая.
Вызов:
имя_вещ_перем=atof(имя_строки);
преобразует строку из цифр имя_строки в соответствующее вещественное
зна?ение и присваивает его переменной имя_перем, заранее описанной как
вещественная (float).
Вызов:
itoa(имя_цел_перем, имя_строки, 10_или_16);
преобразует зна?ение целой переменной имя_цел_перем в строку и помещает
ее имя_строки; зна?ение записывается в десяти?ном виде, если третий
аргумент равен 10, или в шестнадцатири?ном - если 16.
Пример 1. Вы?ленение ?исла и его преобразование в зна?ение. В файле,
идентифицируемом переменной in есть строка ПАРАМЕТР=25,которая с?итывается
в символьный массив buf. Символы buf перебираются в цикле, пока не
встретится символ =. Последующие символы записываются во вспомогательную
строку buf1, которую завершают спецсимволом \0. Затем buf1 преобразуется к
целому зна?ению, которое присваивается целой переменной par.
fgets(buf, 80, in);
i=0;
j=0;
do
{
if(j!=0)buf1[i-j]=buf[i];
if(buf[i]=='=')j=i-1;
i=i+1;
} while (buf[i]!='\n');
buf1[i-j]='\0';
par=atoi(buf1);
Пример 2. Приписывание имени файла нового расширения. В строке infile
хранится имя входного файла программы. Имя выходного файла должно быть
таким же, но с расширением res, и храниться в строке outfile. Элементам
outfile в цикле присваиваются зна?ения элементов infile, пока в infile не
встретится то?ка - разделитель имени и расширения. Затем в конец outfile
приписывают спецсимвол \0 и сцепляют outfile со строковой константой
".res".
i=0;
while (infile[i]!='.')
{
outfile[i]=infile[i];
i=i+1;
}
outfile[i]='\0';
strcat(outfile, ".res");
ДИНАМИЧЕСКОЕ РАСПРЕДЕЛЕНИЕ ПАМЯТИ
Помимо описания массивов фиксированного размера с выделением заданного
коли?ества памяти перед выполнением программы, возможно также выделение
памяти в процессе выполнения программы, в коли?естве, определяемом также в
процессе выполнения программы - динами?еское распределение памяти.
Динами?еское распределение памяти позволяет добиться строгого соответствия
коли?ества выделяемой памяти потребностям зада?и при конкретном исполнении
программы. С одной стороны, возможно сократить использование памяти,
выделяя и освобождая ее по мере необходимости. С другой - возможно обойти
ранее упоминавшееся ограни?ение на размер массива, расширив его практи?ески
до размеров всей свободной памяти.
Если в программе предполагается использование динами?еского
распределения памяти, файл с исходным текстом должен содержать директиву
include . Память выделяется под массивы, имена которых описаны
как указатели соответствующих типов. После выделения памяти под массив
обращение к его элемента производится обы?ным образом.
Оператор
имя_масс=(назв_типа)malloc(коли?_байт);
выделяет под массив имя_масс память в коли?естве коли?_байт байтов. Для
определения зна?ения коли?_байтов ?аще всего используется выражение вида
длина_масс*sizeof(назв_типа).
При выделении памяти под двумерный массив, описанный как указатель на
массив указателей (назв_типа **имя_масс;) сперва выделяется память под
массив указателей
**имя_масс=malloc(длина_масс1*sizeof(назв_типа));
а затем в цикле выделяется память под каждый элемент этого массива
указателей.
При невозможности выделить память в требуемом коли?естве указателю
присваивается зна?ение NULL. Вообще говоря, следует проверять, удалось ли
выделить память под массив.
Если массив, под который выделена память, не используется в программе с
некоторого шага, то перед этим шагом можно (и нужно) освободить память.
Вызов вида:
free(имя_масс);
освобождает память, динами?ески выделенную ранее под массив имя_масс.
Пример. Выделение памяти под двумерный массив вещественных ?исел matr
размера m x n (из m массивов по n элементов каждый). Упомянутые переменные
и переменная цикла должны быть описаны заранее. Кроме того, следует
предусмотреть обработку ситуации, когда память не удалось выделить (здесь
это - вызов заранее описанной функции terminate).
**matr=(double**)malloc(m*sizeof(double*));
if(*matr==NULL)terminate();
for(i=0;i {
matr[i]=(double*)malloc(n*sizeof(double));
if(matr[i]==NULL)terminate();
}
НЕСКОЛЬКО СООБРАЖЕНИЙ ПО ОТЛАДКЕ
Функции, из которых состоит программа следует отлаживать по одной. Это
следует делать даже в том слу?ае, если окон?ательный текст программы
предполагается хранить в одном файле.
Отладка состоит из устранения ошибок трансляции и доводки алгоритма.
Об ошибках трансляции сообщают компилятор и редактор связей. Наиболее
?астые ошибки связаны с естественными огрехами при наборе текста программы
и с забыв?ивостью. К ним относятся: непроставленная ; в конце оператора;
перепутанные ; и , при описании переменной или функции; незакрытые простые
скобки при описании длинных арифмети?еских и логи?еских выражений или
фигурные скобки при записи вложенных (а то и обы?ных) операторов ветвления
и цикла. Кроме того встре?аются неописанные переменные или функции и
неправильно набранные имена описанных переменных и функций.
Следует также иметь ввиду, ?то одна ошибка из вышеназванных может
повле?ь невосприятие компилятором последующих строк, самих по себе
синтакси?ески правильных. Довольно ?асто при?иной сообщения об ошибке в
какой-то строке является ошибка, сделанная выше, из-за которой весь
последующий текст стал бессмысленным с то?ки зрения компилятора; устранение
такой ошибки устраняет и ею порожденные.
Ошибки могут быть препятствующими порождению объектного модуля (error)
или не препятствующим ему (warnings). Эти последние - предупреждения -
ни?ем не лу?ше "настоящих" ошибок, и должны обязательно просматриваться и
устраняться. Предупреждения, связанные с указателями, ?асто говорят о
неправильной работе с массивами. Предупреждение о несоответствии типов
?асто связано с нарушением порядка следования аргументов при вызове
функции.
После устранения ошибок в записи алгоритма на?инается отладка самого
алгоритма. Их можно разделить на ошибки выполнения (аварийные завершения) и
ошибки алгоритма. Ошибки выполнения связаны ?аще всего с арифметикой или
математи?ескими функциями и возникают при "мелких" ошибках алгоритма. К ним
можно отнести выход за границу массива (например, в цикле при неверном
условии выхода), ошибка в знаке или вы?итание одинаковых зна?ений (из-за
перепутанных сходных имен переменных). "Крупные" ошибки алгоритма могут не
приводить к аварийным завершениям, но давать физи?ески бессмысленные
результаты рас?ета.
Поиск ошибок производится путем контроля зна?ений тех или иных
переменных в процессе выполнения программы. Для этого используются средства
среды программирования на каждом шаге при пошаговом выполнении программы
или в то?ках останова. Кроме того, используется вспомогательный
промежуто?ный вывод на дисплей или в файл.
О?евидно, ?то отладка ведется на примере, для которого известно решение
(коне?ные зна?ения). Пример должен быть достато?но простым, ?тобы можно
было проследить промежуто?ные зна?ения, и достато?но общим, ?тобы заходить
по возможности во все ветви алгоритма. Где взять такой пример - вопрос
отдельный и не всегда разрешимый.