Главная страница

Курс на Си. Подбельский. Курс программирования на Си. В., Фомин С. С. Курс программирования на языке Си Учебник


Скачать 1.57 Mb.
НазваниеВ., Фомин С. С. Курс программирования на языке Си Учебник
АнкорКурс на Си
Дата18.02.2023
Размер1.57 Mb.
Формат файлаdocx
Имя файлаПодбельский. Курс программирования на Си.docx
ТипУчебник
#943863
страница7 из 42
1   2   3   4   5   6   7   8   9   10   ...   42
Глава 2

ВВЕДЕНИЕ

В ПРОГРАММИРОВАНИЕ НА СИ

    1. Структура и компоненты простой программы

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

Текст программы и препроцессор. Каждая программа на языке Си есть последовательность препроцессорных директив, описаний и определений глобальных объектов и функций. Препроцессорные директивы (в главе 1 мы упоминали директивы #include и #define) управляют преобразованием текста программы до ее компиляции. Определения вводят функции и объекты. Объекты необходимы для представления в программе обрабатываемых данных. Функции определяют потенциально возможные действия программы. Описа­ния уведомляют компилятор о свойствах и именах тех объектов и функций, которые определены в других частях программы (напри­мер, ниже по ее тексту или в другом файле).

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

Определения и описания программы на языке Си могут разме­щаться в строках текстового файла достаточно произвольно (в сво­бодном формате). Для препроцессорных директив существуют огра­ничения. Во-первых, препроцессорная директива обычно размещает­ся в одной строке, то есть признаком ее окончания является признак конца строки текста программы. Во-вторых, символ '#', вводящий каждую директиву препроцессора, должен быть первым отличным от пробела символом в строке с препроцессорной директивой.

Подробному изучению возможностей препроцессора и всех его директив будет посвящена глава 3. Сейчас достаточно рассмотреть только основные принципы работы препроцессора и изложить об­щую схему подготовки исполняемого модуля программы, написан­ной на языке Си. Исходная программа, подготовленная на языке Си в виде текстового файла, проходит три обязательных этапа об­работки (рис. 2.1):

  • препроцессорное преобразование текста;

  • компиляция;

  • компоновка (редактирование связей или сборка).

Только после успешного завершения всех перечисленных этапов формируется исполняемый машинный код программы.

Задача препроцессора - преобразование текста программы до ее компиляции. Правила препроцессорной обработки определяет про­граммист с помощью директив препроцессора. Каждая препроцес- сорная директива начинается с символа '#'. В этой главе нам будет достаточно директивы #include.

Препроцессор «сканирует» исходный текст программы в поиске строк, начинающихся с символа '#'. Такие строки воспринимают­ся препроцессором как команды (директивы), которые определяют действия по преобразованию текста. Директива #include опреде­ляет, какие текстовые файлы нужно включить в этом месте текста программы.

Директива #include <...> предназначена для включения в текст программы текста файла из каталога «заголовочных файлов», по­ставляемых вместе со стандартными библиотеками компилятора. Каждая библиотечная функция, определенная стандартом язы-



Рис. 2.1. Схема подготовки исполняемой программы

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

Важно понимать, что употребление в программе препроцессорной директивы

#include < имя_заголовочного_файла >

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

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

Термин «заголовочный файл» (headerfile) в применении к фай­лам, содержащим описания библиотечных функций стандартных библиотек, не случаен. Он предполагает включение этих файлов именно в начало программы. Мы настоятельно рекомендуем, чтобы до обращения к любой функции она была определена или описа­на в том же файле, где помещен текст программы. Описание или определения функций должны быть «выше» по тексту, чем вызовы функций. Именно поэтому заголовочные файлы нужно помещать в начале текста программы, то есть заведомо раньше обращений к соответствующим библиотечным функциям.

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

Структура программы. После выполнения препроцессорной об­работки в тексте программы не остается ни одной препроцессорной директивы. Теперь программа представляет собой набор описаний и определений. Если не рассматривать (в этой главе) определений глобальных объектов и описаний, то программа будет набором опре­делений функций.

Среди этих функций всегда должна присутствовать функция с фиксированным именем main. Именно эта функция является глав­ной функцией программы, без которой программа не может быть выполнена. Имя этой главной функции для всех программ одинако­во (всегда main) и не может выбираться произвольно. Таким обра­зом, исходный текст программы в простом случае (когда программа состоит только из одной функции) имеет такой вид:

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

int main( )

{

определения_объектов;

операторы;

return 0;

}

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

Перед именем каждой функции программы следует помещать све­дения о типе возвращаемого функцией значения (тип результата). Ес­ли функция ничего не возвращает, то указывается тип void. Функция main( ) является той функцией программы, которая запускается на исполнение по командам операционной системы. Возвращаемое функ­цией main( ) значение также передается операционный системе. Если программист не предполагает, что операционная система будет анали­зировать результат выполнения его программы, то проще всего ука­зать, что возвращаемое значение отсутствует, то есть имеет тип void.

Каждая функция (в том числе и main) в языке Си должна иметь набор параметров. Этот набор может быть пустым, тогда в скоб­ках после имени функции помещается служебное слово void либо скобки остаются пустыми. В отличие от обычных функций, главная функция main( ) может использоваться как с параметрами, так и без них. Состав списка параметров функции main( ) и их назначение будут рассмотрены в главе 5. Сейчас только отметим, что параметры функции main( ) позволяют организовать передачу данных из среды выполнения в исполняемую программу, минуя средства, предостав­ляемые стандартной библиотекой ввода-вывода.

Вслед за заголовком void main( ) размещается тело функции. Тело функции - это блок, последовательность определений, описа­ний и исполняемых операторов, заключенная в фигурные скобки. Определения и описания в блоке будем размещать до исполняемых операторов. Каждое определение, описание и каждый оператор за­вершаются символом ';' (точка с запятой).

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

Чтобы привести пример простейшей осмысленной программы на языке Си, необходимо ввести оператор, обеспечивающий вывод дан­ных из ЭВМ, например на экран дисплея. К сожалению (или как особенность языка), такого оператора в языке Си НЕТ! Все возмож­ности обмена данными с внешним миром программа на языке Си реализует с помощью библиотечных функций ввода-вывода.

Для подключения к программе описаний средств ввода-вывода из стандартной библиотеки компилятора используется директива #include <stdio.h>.

Название заголовочного файла stdio.h является аббревиатурой: std - standard (стандартный), i - input (ввод), o - output (вывод), h - head (заголовок).

Функция форматированного вывода. Достаточно часто для вы­вода информации из ЭВМ в программах используется функция printf( ). Она переводит данные из внутреннего кода в символь­ное представление и выводит полученные изображения символов результатов на экран дисплея. При этом у программиста имеется возможность форматировать данные, то есть влиять на их представ­ление на экране дисплея.

Возможность форматирования условно отмечена в самом имени функции с помощью литеры f в конце ее названия (printformatted).

Оператор вызова функции printf( ) можно представить так:

printf (форматная_строка, список_аргументов);

Форматная строка ограничена двойными кавычками (см. стро­ковые константы, §1.2) и может включать произвольный текст, управляющие символы и спецификации преобразования данных. Список аргументов (с предшествующей запятой) может отсутство­вать. Именно такой вариант использован в классической первой программе на языке Си [1, 2]:

#include

void main( ) {

printf ("\n Здравствуй, Мир!\п");

}

Директива #include <stdio.h> включает в текст программы опи­сание (прототип) библиотечной функции printf( ). (Если удалить из текста программы эту препроцессорную директиву, то появятся сообщения об ошибках и исполнимый код программы не будет соз­дан. Среди параметров функции printf( ) есть в этом примере только форматная строка (список аргументов отсутствует). В форматной строке два управляющих символа '\n' - «перевод строки». Между ними текст, который выводится на экран дисплея:

Здравствуй, Мир!

Первый символ '\n' обеспечивает вывод этой фразы с начала новой строки. Второй управляющий символ '\n' переведет курсор к началу следующей строки, где и начнется вывод других сообщений (не связанных с программой) на экран дисплея.

Итак, произвольный текст (не спецификации преобразования и не управляющие символы) непосредственно без изменений выво­дится на экран. Управляющие символы (перевод строки, табуляция и т. д.) позволяют влиять на размещение выводимой информации на экране дисплея.

Спецификации преобразования данных предназначены для управления формой внешнего представления значений аргументов функции printf( ). Обобщенный формат спецификации преобразо­вания имеет вид: %флажки ширина_поля.точность модификатор спецификатор

Среди элементов спецификации преобразования обязательными являются только два - символ '%' и спецификатор.

В задачах вычислительного характера этой главы будем исполь­зовать спецификаторы:

d - для целых десятичных чисел (тип int);

u - для целых десятичных чисел без знака (тип unsigned);

f - для вещественных чисел в форме с фиксированной точкой (типы float и double);

e - для вещественных чисел в форме с плавающей точкой (с ман­тиссой и порядком) - для типов double и float;

g - наиболее компактная запись из двух вариантов: с плавающей или фиксированной точкой.

В список аргументов функции printf( ) включают объекты, зна­чения которых должны быть выведены из программы. Это выраже­ния и их частные случаи - переменные и константы. Количество аргументов и их типы должны соответствовать последовательности спецификаций преобразования в форматной строке. Например, ес­ли вещественная переменная summa имеет значение 2102.3, то при таком вызове функции

printf(«\n summa=%f», summa);

на экран с новой строки будет выведено:

summa=2102.3

После выполнения операторов

float c, e;

int k;

c=48.3; k=-83; e=16.33;

printf ("\nc=%f\tk=%d\te=%e", c, k, e);

на экране получится такая строка:

c=48.299999 k=-83 e=1.63300e+01

Здесь обратите внимание на управляющий символ '\t' (табуляция). С его помощью выводимые значения в строке результата от­делены друг от друга.

Для вывода числовых значений в спецификации преобразования весьма полезны «ширина_поля» и «точность».

Ширина_поля - целое положительное число, определяющее дли­ну (в позициях на экране) представления выводимого значения.

Точность - целое положительное число, определяющее количест­во цифр в дробной части внешнего представления вещественного числа (c фиксированной точкой) или его мантиссы (при использо­вании формы с плавающей точкой).

Пример с теми же переменными:

printf ("\nc=%5.2\tk=%5d\te=%8.2f\te=%11.4e", c, k, e, e);

Результат на экране:

c=48.30 k= —83 e= 16.33 e= 1.6330e+01

В качестве модификаторов в спецификации преобразования ис­пользуются символы:

h - для вывода значений типа short int;

l - для вывода значений типа long;

L - для вывода значений типа long double.

Примеры на использование модификаторов пока приводить не будем.

Хотя в разделе, посвященном символам и строковым константам (§1.2), упоминалось о возможностях записи управляющих последо­вательностей и эскейп-последовательностей внутри строк, остано­вимся еще раз на этом вопросе в связи с форматной строкой. При

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

При необходимости вывести символ % в форматную строку его включают дважды: % %.

Применимость вещественных данных. Даже познакомившись с различиями в диапазонах представления вещественных чисел, на­чинающий программист не сразу осознает различия между типами float, double и long double. Прежде всего бросается в глаза разное количество байтов, отводимых в памяти для вещественных данных перечисленных типов. На современных ПК:

  • для float - 4 байта;

  • для double - 8 байт;

  • для long double - 10 байт.

По умолчанию все константы, не относящиеся к целым типам, принимают тип double. У программиста это соглашение часто вы­зывает недоумение - а не лучше ли всегда работать с веществен­ными данными типа float и только при необходимости переходить к double или long double? Ведь значения больше 1Е+38 и меньше 1Е-38 встречаются довольно редко.

Следующая программа (предложена С. М. Лавреновым) иллюст­рирует опасности, связанные с применением данных типа float даже в несложных арифметических выражениях:

#include

void main( )

{

float a, b, c, t1, t2, t3;

a=95.0;

b=0.02;

t1=(a+b)*(a+b);

t2=-2.0*a*b-a*a;

t3=b*b;

c=(t1+t2)/t3;

printf("\nc=%f\n", c);

}

Результат выполнения программы:

c=2.441406

Если в той же программе переменной а присвоить значение 100.0, то результат будет еще хуже:

c=0.000000.

Таким образом, запрограммированное с использованием перемен­ных типа float несложное алгебраическое выражение

(tz + Z>)5 - (a2 +2ab)

b2

никак «не хочет» вычисляться и принимать свое явное теоретиче­ское единичное значение.

Если заменить в программе только одну строку, то есть так опре­делить переменные:

double a, b, c, t1, t2, t3;

значение выражения вычисляется совершенно точно: c=1.000000

Компилятор просматривает символы (литеры) текста программы слева направо. При этом его первая задача - выделить лексемы язы­ка. За очередную лексическую единицу принимается наибольшая последовательность литер, которая образует лексему. Таким обра­зом, из последовательности int_line компилятор не станет выделять как лексему служебное слово int, а воспримет всю последователь­ность как введенный пользователем идентификатор.

В соответствии с тем же принципом выражение d+++b трактуется как d++ +b, а выражение b-->c эквивалентно (b--)>c.

Следующая программа иллюстрирует сказанное:

#include void main()

{ int n=10,m=2;

printf("\nn+++m=%d",n+++m);

printf("\nn=%d, m=%d",n,m);

printf("\nm-->n=%d",m-->n);

printf("\nn=%d, m=%d",n,m);

printf("\nn-->m=%d",n-->m);

printf("\nn=%d, m=%d",n,m);

}

Результат выполнения программы:

n+++m=12

n=11,m=2 m-->n=0 n=11,m=1 n-->m=1 n=10,m=1

Результаты вычисления выражений n+++m, n-->m, m-->n полностью соответствуют правилам интерпретации выражений на основе таблицы рангов операций (см. табл. 1.4). Унарные операции ++ и — имеют ранг 2. Аддитивные операции + и - имеют ранг 4. Операции отношений имеют ранг 6.

    1. Элементарные средства

программирования

Группы операторов языка Си. Если вспомнить вопросы, перечис­ленные в начале главы 1, то окажется, что мы уже получили ответы на многие из них. Введены алфавит языка и его лексемы; приведены

основные типы данных, константы и переменные; определены все операции; рассмотрены правила построения арифметических вы­ражений, отношений и логических выражений; описана структура программы; рассмотрены средства вывода из ЭВМ арифметических значений с помощью функции printf( ); определен оператор при­сваивания.

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

Вернемся вновь к структуре простой программы, состоящей толь­ко из одной функции с именем main( ).

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

int main( )

{ определения_объектов;

операторы;

return 0;

}

Как мы уже договорились, пока нам будет достаточно препро- цессорной директивы #include <...>, В качестве определяемых объ­ектов будем вводить переменные и константы базовых типов. А вот об операторах в теле функции нужно говорить подробно.

Каждый оператор определяет действия программы на очередном шаге ее выполнения. У оператора (в отличие от выражения) нет значения. По характеру действий различают два типа операторов: операторы преобразования данных и операторы управления работой программы.

Наиболее типичные операторы преобразования данных - это операторы присваивания и произвольные выражения, завершенные символом «точка с запятой»:

i++; /*Арифметическое выражение - оператор*/ x*=i; /*Оператор составного присваивания*/ i=x-4*i; /*Оператор простого присваивания*/

Так как вызов функции является выражением с операцией «круг­лые скобки» и операндами «имя функции», «список аргументов», к операторам преобразования данных можно отнести и оператор вызова или обращения к функции:

имя_функции (список_ аргументов);

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

Операторы управления работой программы:

  • составные операторы;

  • операторы выбора;

  • операторы циклов;

  • операторы перехода.

К составным операторам относят собственно составные операто­ры и блоки. В обоих случаях это последовательность операторов, за­ключенная в фигурные скобки. Отличие блока от составного опера­тора - наличие определений в теле блока. Например, приведенный ниже фрагмент программы - составной оператор:

{

n++;

summa+=(float)n;

}

а этот фрагмент - блок:

{

int n=0;

n++;

summa+=(float)n;

}

Наиболее часто блок употребляется в качестве тела функции.

Операторы выбора - это условный оператор (if) и переключатель (switch).

Операторы циклов в языке Си трех видов - с предусловием (while), с постусловием (do) и параметрический (for).

Операторы перехода выполняют безусловную передачу управ­ления: goto (безусловный переход), continue (завершение текущей итерации цикла), break (выход из цикла или переключателя), return (возврат из функции).

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

Условный оператор имеет 2 формы: сокращенную и полную. Со­кращенная форма:

if (выражение_условие) оператор;

где в качестве выражения_условия могут использоваться: ариф­метическое выражение, отношение и логическое выражение. Опе­ратор, включенный в условный, выполняется только в случае ис­тинности (то есть при ненулевом значении) выражения_условия. Пример:

if (x < 0 && x > -10) x=-x:

Полная форма условного оператора:

if (выражение_условие)

оператор_1;

else

оператор_2;

Здесь в случае истинности выражения_условия выполняется только оператор_1. Если значение выражения_условия ложно, то выполняется только оператор_2. Например:

if (x > 0) b=x;

else b=-x;

Выполнение условного оператора иллюстрируют схемы на рис. 2.2.

Обратим внимание на то, что в условных операторах в качестве любого из операторов (после условия или после else) может ис­пользоваться составной оператор. Например, при решении алгеб­раического уравнения 2-й степени ax2 + bx + c = 0 действительные корни имеются только в случае, если дискриминант (b2 - 4ac) не­отрицателен.

Следующий фрагмент программы иллюстрирует использование условного оператора при определении действительных корней x1, x2 квадратного уравнения:



Рис. 2.2. Схемы условных операторов (выражение-условие - условие после if): a- сокращенная форма; б - полная форма

d=b*b - 4*a*c; /* d - дискриминант */

if (d>=0.0)

{

x1=(-b+sqrt(d))/2/a;

x2=(-b-sqrt(d))/2/a;

printf("\n Корни: x1=%e, x2=%e", x1, x2);

}

else

printf("\n Действительные корни отсутствуют.");

Во фрагменте предполагается, что переменные d, b, a, x1, x2 - ве­щественные (типа float либо double). До приведенных операторов переменные a, b, c получили конкретные значения, для которых вы­полняются вычисления. В условном операторе после if находится со­ставной оператор, после else - только один оператор - вызов функ­ции printf( ). При вычислении корней используется библиотечная функция sqrt( ) из стандартной библиотеки компилятора. Ее про­тотип находится в заголовочном файле math.h (см. приложение 3).

Метки и пустой оператор. Метка - это идентификатор, поме­щаемый слева от оператора и отделенный от него двоеточием «:». Например:

СОН: X+=-8;

Чтобы можно было поставить метку в любом месте программы (или задать пустое тело цикла), в язык Си введен пустой оператор, изображаемый только одним символом «;». Таким образом, можно записать такой помеченный пустой оператор:

МЕТКА:;

Оператор перехода. Оператор безусловного перехода имеет сле­дующий вид:

goto идентификатор;

где идентификатор - одна из меток программы. Например:

goto COH; или goto МЕТКА;

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

Программа оценки машинного нуля. В вычислительных задачах при программировании итерационных алгоритмов, завершающихся при достижении заданной точности, часто нужна оценка «машин­ного нуля», то есть числового значения, меньше которого невоз­можно задавать точность данного алгоритма. Абсолютное значение «машинного нуля» зависит от разрядной сетки, применяемой ЭВМ, от принятой в конкретном трансляторе точности представления ве­щественных чисел и от значений, используемых для оценки точ­ности. Следующая программа оценивает абсолютное значение «ма­шинного нуля» относительно единицы, представленной переменной типа float:

1

/* Оценка "машинного

нуля" */

2

#include




3

void main( )




4

{ int k; /*k —

счетчик итераций*/

5

float e,el; /*e1 -

вспомогательная переменная*/

6

e=1.0; /*e -

формируемый результат*/

7

k=0;




8 M:e=e/2.0;

9 e1=e+1.0;

10 k=k+1;

11 if (e1>1.0) goto M;

12 printf ("\n число делений на 2: %6d\n", k);

13 printf ("машинный нуль: %e\n", e);

14 }

В строках программы слева помещены порядковые номера, ко­торых нет в исходном тексте. Номера добавлены только для удоб­ства ссылок на операторы. Строка 1 - комментарий с названием программы. Комментарии в строках 4, 5, 6 поясняют назначение переменных. Объяснить работу программы проще всего с помощью трассировочной таблицы (табл. 2.1).

Таблица 2.1. Трассировочная таблица

Шаг выполнения

Номер строки

Значения переменных

e

k

e1

1

6

1.0





2

7

1.0

0



3

8

0.5

0




4

9

0.5

0

1.5

5

10

0.5

1

1.5

6

11

0.5

1

1.5

7

8

0.25

1

1.5

8

9

0.25

1

1.25

9

10

0.25

2

1.25

10

11

0.25

2

1.25

11

8

0.125

2

1.25
















Во втором столбце таблицы указаны номера строк с исполняе­мыми операторами. Значения переменных даны после выполнения соответствующего оператора. Только что измененное значение пере­менной в таблице выделено. После подготовительных присваиваний (строки 6, 7) циклически выполняются операторы 8-11 до тех пор, пока истинно отношение e1>1.0, проверяемое в условном операторе. При каждой итерации значение переменной e уменьшается вдвое, и, наконец, прибавление (в строке 9) к 1.0 значения e не изменит результата, то есть e1 станет равно 1.0.

При использовании компилятора gcc ОС FreeBSD получен сле­дующий результат:

Число делений на 2: 24

Машинный нуль: 5.960464е-08

При использовании в строке 5 для определения переменных e, e1 типа double, то есть при использовании двойной точности, получен иной результат:

Число делений на 2: 53

Машинный нуль: 1.110223е-16

Оба результата не хуже значений, приведенных в приложении 2, для предельных констант FLT_EPSILON и DBL_EPSILON.

Ввод данных. Для ввода данных с клавиатуры ЭВМ в програм­ме будем использовать функцию (описана в заголовочном файле stdio.h):

scanf (форматная_строка, список_аргументов);

Функция scanf( ) выполняет «чтение» кодов, вводимых с клавиа­туры. Это могут быть как коды видимых символов, так и управ­ляющие коды, поступающие от вспомогательных клавиш и от их сочетаний. Функция scanf( ) воспринимает коды, преобразует их во внутренний формат и передает программе. При этом программист может влиять на правила интерпретации входных кодов с помощью спецификаций форматной строки. (Возможность форматирования условно отмечена в названии функции с помощью литеры f в конце имени.)

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

% * ширина_поля модификатор спецификатор

Среди элементов спецификации преобразования обязательны только % и спецификатор. Для ввода числовых данных использу­ются спецификаторы:

d - для целых десятичных чисел (тип int);

u - для целых десятичных чисел без знака (тип unsigned int);

f - для вещественных чисел (тип float);

e - для вещественных чисел (тип float).

Ширина_поля - целое положительное число, позволяющее опре­делить, какое количество байтов (символов) из входного потока соответствует вводимому значению. Этим элементом мы сейчас не будем пользоваться.

Звездочка '*' в спецификации преобразования позволяет пропус­тить во входном потоке байты соответствующего вводимого значе­ния. (Сейчас, когда уже забыли о подготовке данных на перфокар­тах и перфолентах, звездочка при вводе почти не используется. Она может быть полезной при чтении данных из файлов, когда нужно пропускать те или иные значения.)

В качестве модификаторов используются символы:

h - для ввода значений типа short int (hd);

l - для ввода значений типа long int (ld) или double (lf, le);

  1. - для ввода значений типа long double (Lf, Le);

  2. - для ввода значений типа long long.

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

В отличие от функции printf( ), аргументами для функции scanf( ) могут быть только адреса объектов программы, в частном случае - адреса ее переменных. Не расшифровывая понятия адре­са (адресам и указателям будет посвящена глава 4), напомним, что в языке Си имеется специальная унарная операция & получения адреса объекта:

& имя_объекта

Выражение для получения адреса переменной будет таким:

& имя_переменной

Итак, для обозначения адреса перед именем переменной записы­вают символ &. Если name - имя переменной, то &name - ее адрес.

Например, для ввода с клавиатуры значений переменных n, z, x можно записать оператор:

scanf ("%d%f%f",&n,&z,&x);

В данном примере спецификации преобразования в форматной строке не содержат сведений о размерах полей и точностях вводи­мых значений. Это разрешено и очень удобно при вводе данных, диапазон значений которых определен не строго. Если переменная n описана как целая, z и x - как вещественные типа float, то пос­ле чтения с клавиатуры последовательности символов 18 18 -0.431 переменная n получит значение 18, z - значение 18.0, x - значение -0.431.

При чтении входных данных функция scanf( ) (в которой специ­фикации не содержат сведений о длинах вводимых значений) вос­принимает в качестве разделителей полей данных «обобщенные пробельные символы» - собственно пробелы, символы табуляции, символы новых строк. Изображения этих символов на экране от­сутствуют, но у них имеются коды, которые «умеет» распознавать функция scanf( ). При наборе входной информации на клавиатуре функция scanf( ) начинает ввод данных после нажатия клавиши Enter. До этого набираемые на клавиатуре символы помещаются в специально выделенную операционной системой область памяти - в буфер клавиатуры и одновременно отображаются на экране в виде строки ввода. До нажатия клавиши Enter разрешено редактировать (исправлять) данные, подготовленные в строке ввода.

Рассмотрим особенности применения функции scanf( ) для ввода данных и принципы построения простых программ на основе сле­дующих несложных задач вычислительного характера.

Вычисление объема цилиндра. В предыдущих задачах не тре­бовалось вводить исходные данные. После выполнения программы на экране появлялся результат. Более общий случай - программа, требующая ввода исходных данных:

  1. /*Вычисление объема прямого цилиндра*/

  2. #include

  3. void main( )

  4. {

  5. double h, r, v;

  6. const float PI = 3.14159;

  7. /*h — высота цилиндра, r — радиус цилиндра*/

  8. /*v — объем цилиндра, PI - число "пи" */

  9. printf("\n Радиус цилиндра r= ");

Содержание 3

ПРЕДИСЛОВИЕ 12

1   2   3   4   5   6   7   8   9   10   ...   42


написать администратору сайта