
информатика_2 / Программирование_С
.pdf
|
|
|
|
Таблица 1.6 |
|
|
Примеры определения типа выражения |
||
|
|
|
|
Результат |
|
Операнды |
|
Операции |
|
|
Целые |
|
+ – * / % |
целый |
|
Вещественные |
|
+ – * / - |
вещественный |
|
Числовые |
или |
> < != == >= <= |
логический (int) |
|
символьные |
|
|
логический |
|
Логические |
|
&& ! || |
|
|
|
|
|
|
Если тип вычисленного выражением значения не совпадает с типом переменной левой части, то происходит преобразование (приведение) типов, о котором далее в отдельном разделе.
1.2.6.3. Порядок вычисления выражений
Порядок вычисления выражений определяется рангом (приоритетом) входящих в него операции и правилами ассоциативности, которые применяются при вычислении значений выражений, в которых несколько операций одного приоритета стоят подряд. Большинство операций левоассоциативны (→), то есть вычисляются слева направо естественным образом (например, бинарные операции). Однако есть правоассоциативные операции (←), которые вычисляются справа налево, и в этом есть глубокий смысл (например, операции присваивания), что позволяет выполнить цепочку присваиваний, например:
х = y = z = 10;
Здесь цепочка раскрывается по порядку z = 10, затем y = z, затем x = y.
В таблице 1.7 приведены приоритеты операций С. Из таблицы видно, что принятый ранг операций наиболее близок к математическому, также как и принятый порядок их вычисления. Так, умножение и деление (мультипликативные операции) старше сложения и вычитания (аддитивные операции). Унарные операции + и – старше бинарных, стало быть, знак операнда вычисляется в первую очередь. Операция присваивания и ее клоны младше прочих, что позволяет выполнить присваивание только после того, как значение выражения вычислено полностью. Операции отношения младше арифметических операций, что позволяет использовать естественную запись логических выражений, например, x>0 && y>0. Здесь в первую очередь вычисляются значения отношений, которые затем являются операндами конъюнкции.
Операции ++ и – – приведены в таблице с рангом 2, но на самом деле эти операции имеют две формы:
префиксная: ++x, – – x. При этом операция инкремента (декремента) старше всех прочих, входящих в запись выражения, и выполняется в первую очередь,
постфиксная: x ++, x – –. При этом операция инкремента (декремента) младше всех прочих, входящих в запись выражения, и выполняется в последнюю очередь.
21

|
|
|
|
|
|
|
Таблица 1.7 |
||
|
|
Полная таблица приоритетов операций С |
|||||||
|
|
|
|
Ассоциативность |
|
|
|||
Ранг (приоритет) |
|
|
Операции |
|
|||||
|
|
() |
[] |
–> |
. |
(операции |
→ |
|
|
|
|
разыменования) |
|
|
← |
|
|
||
|
|
! ~ + – ++ – – |
& |
* (тип) sizeof |
|
|
|||
|
|
* / % |
(бинарные) |
|
→ |
|
|
||
|
|
+ – |
(бинарные) |
|
→ |
|
|
||
|
|
<< >> |
|
(сдвиг) |
|
|
→ |
|
|
|
|
< <= > >= (отношения) |
→ |
|
|
||||
|
|
== != (отношения) |
|
→ |
|
|
|||
|
|
& |
|
|
|
|
→ |
|
|
|
|
^ (поразрядное исключающее или) |
→ |
|
|
||||
|
|
| |
|
|
|
|
→ |
|
|
|
|
&& |
|
|
|
|
→ |
|
|
|
|
|| |
|
|
|
|
→ |
|
|
|
|
? : (условная операция) |
← |
|
|
||||
|
|
= *= /= %= += –= &= ^= |= <<= >>= |
← |
|
|
||||
|
|
, (операция запятая) |
|
→ |
|
|
|||
Примеры: |
|
|
|
|
|
|
|
|
|
int c, a = 5, b = 3; |
|
|
|
|
|
|
|
||
c = a++; |
// c = 6, а = 5, |
|
|
|
|
|
|
||
c = a++*3; |
// c = a*3 = 15, после этого а = 6, |
|
|
|
|||||
с = – – а / 2; // – – a, a = 5, потом c = а / 2 = 2, |
|
|
|
||||||
c = a+++b; |
// сначала с = a + b = 8, потом а++, а = 6, |
|
|
|
|||||
c = a+++++b; // ++b, потом +, потом а++. |
|
|
|
|
1.2.6.4. Приведение типов и преобразование типов в выражениях
Под приведением типов понимаются действия, которые компилятор выполняет в случае, когда в выражении смешаны операнды различных типов, чего, строго говоря, не следует допускать. Механизмы приведения типов в С заключаются в том, что при вычислении значения выражения компилятор, не изменяя внутреннего представления данных, преобразует данное «меньшего» типа к «большему», где «величину» типа определяет размер выделенной для него памяти. Такое преобразование выполняется без ведома программиста, и может привести к потере данных:
известно, что целое значение представлено в памяти точно, а вещественное приближенно
int a = 5, b = 2; float c;
c = a * 1.5; // в выражении операнды разных типов,
22
//автоматически включаются механизмы
//приведения типов, с = 7.5.
Для того, чтобы избежать потерь информации, в выражениях следует применять операцию явного преобразования типа, синтаксис которой:
(имя_типа) имя_переменной Эта запись включается в те выражения, в которых необходимо выполнить
преобразование перед вычислением. В этом случае программист явно указывает компилятору, что тот должен сделать.
int a = 5, b = 2; |
|
float c; |
|
с = (float) a / 2.0; |
// c = 5.0 / 2.0 = 2.5, |
b = (int) c / b; |
// b = 2 / 2 = 1. |
Под преобразованием типов в выражениях понимаются действия, выполняемые при присваивании. Пусть в этом же примере
int a = 5, b = 2; float c;
с = a / b;
Хочется думать, что значение с будет равно 2.5, ведь оно вещественное, но порядок операций таков, что деление старше присваивания, и оно выполняется с операндами целого типа, и его результат = 2, то есть приведение типа будет выполнено только при присваивании.
Для явного преобразования типов используется известный прием: c = (float) a / (float) b; // c = 2.5.
При выполнении присваивания, если типы левой и правой части не совпадают, также происходит неявное преобразование. Компилятор С всегда пытается это сделать, и упрощенно можно считать, что преобразование происходит без потери данных от меньшего типа к большему, например от int к float или от char к int, и с потерей данных из большего типа к меньшему, например, от float к int. Это легко понять, если вспомнить, что тип данного — это объем занятой им памяти.
Явное преобразование выполняется при присваивании вида имя = (тип) выражение;
Рекомендуется строго относиться к типам данных, не смешивать типы в выражениях, следить, чтобы тип левого операнда присваивания соответствовал типу выражения правой части.
1.2.7. Структура и компоненты простой программы на языке С. Функция main
Программа на языке С состоит из одного или нескольких текстовых файлов с расширением «.с» или «.сpp». Если файлов несколько, они объединяются в файл проекта с расширением «.prj». Простейшие программы содержатся в одном текстовом файле.
23
Программа может состоять из одной или нескольких функций. Одна из функций должна иметь имя «main». С этой функции всегда начинается выполнение программы. Любая функция, кроме «main», вызывается из другой функции. При вызове функции могут быть переданы параметры (данные). По окончании выполнения функции в вызывающую функцию может быть возвращено значение (результат), а может не быть возвращено ничего. Примером функций являются библиотечные функции, например, sin(x), fabs(a).
Функция обладает типом, соответствующим возвращаемому значению. Если функция ничего не возвращает, ее тип void. Если функция не имеет аргументов, вместо них в списке параметров записывается слово void.
Пример: |
|
void main (void) |
// не имеет типа и не имеет параметров, |
main () |
// тип функции по умолчанию будет int, |
|
// аргументов нет, значит, их может быть |
|
// произвольное число. |
Пример функций, не возвращающих значения — printf (), scanf ().
1.2.7.1. Комментарии
Комментарии предназначены для записи пояснений и примечаний к тексту программы. Не влияют на выполнение программы. Записываются на родном языке программиста.
В С существуют два вида комментариев.
1. Многострочный комментарий записывается в любом месте текста программы в скобках вида /* */.
Переводит в разряд примечаний весь текст, заключенный между ними. Удобен при отладке программы, чтобы вырезать из текста отдельные фрагменты, не удаляя их физически.
2. Однострочный комментарий записывается в любой строке программы после сочетания символов //.
Комментирует весь текст до окончания строки. Используется для пояснений к строкам.
1.2.7.2. Структура файла программы из одной функции. Блок операторов
Текст программы, которая состоит из одной функции, содержит следующие составляющие, почти все они могут отсутствовать.
#Директивы препроцессора |
// Начинаются с # и записываются в одну |
строку. |
|
Тип_функции main (параметры) // Заголовок функции. |
|
{ // Блок тела функции. |
|
определения объектов; |
|
исполняемые операторы; |
|
return выражение; |
// Если функция возвращает значение. |
} |
|
24
В C объявление переменной (объекта) возможно не только в начале программы, но и в любом месте текста до первого обращения к ней. Областью действия такого объекта является только непосредственно охватывающий его блок. Как правило, так объявляют рабочие переменные.
Определение. Блок (операторов) — это произвольная последовательность определений и операторов, заключенная в фигурные скобки:
{
// блок;
}
Блок используется для укрупнения структуры программы. Точка с запятой в конце блока не ставится.
Текст программы на C обладает структурой. Существует система правил корректной записи текста программ.
Каждый оператор заканчивается знаком «;». Обычная ошибка начинающего — это знак «;», завершающий заголовки функций или операторов цикла. В первом случае синтаксическая ошибка распознается как отсутствие тела функции, во втором случае телом цикла является пустой оператор, что синтаксической ошибкой не является, и программа выполняется.
Каждый оператор записывается в одну строку. Это не необходимо, но очень рекомендуется, так как позволяет структурировать текст программы, наглядно видеть ее алгоритм, и облегчает отладку при пошаговом исполнении.
Блок, то есть произвольный фрагмент текста, заключенный в фигурные скобки { }, размещается в любом месте программы. Использование блоков позволяет укрупнить структуру алгоритма.
Структура программы подчеркивается отступами (опция редактора Indent). Этот простой способ позволяет визуально показать блоки, составляющие структуру алгоритма, всего лишь выделив их в тексте отступами в три позиции для каждой внутренней структуры. Так, отступами принято выделять тело блока, содержимое условного оператора, тело цикла, и любые внутренние вложенные структуры.
Комментарии в тексте необходимы.
Имена объектов программы выбираются осмысленно. Каждое имя подчеркивает назначение и логику объекта, например, имена библиотечных функций sin, abs, printf и прочих говорят сами за себя. Имена объектов, введенные программистом, подчеркивают их абстрактный смысл, например, Count, Square, Point.x, Point.y и так далее.
Пробелы в тексте являются значащими только в составе текстовых констант. В тексте программы пробелы обязаны отделять друг от друга объекты. В остальных случаях их использование произвольно, например, лишние пробелы улучшают читабельность программы.
25
1.2.8. Директивы препроцессорной обработки
Начинаются со знака # и записываются в одной строке. Являются командами (директивами), выполняемыми препроцессором на стадии предварительной обработки текста программы, то есть до ее компиляции. Директив препроцессора достаточно много, на начальном этапе достаточно ознакомиться с двумя из них.
1.2.8.1.Директива #define
Используется для задания именованных констант и для задания строк подстановки.
Синтаксис:
#define имя выражение
Механизм действия директивы — макроподстановки, то есть препроцессор сканирует весь текст программы и выполняет замены в тексте программы, везде вместо «имени» подставляя «выражение».
Замечание: имена define - определенных констант записываются большими буквами, чтобы визуально отличить их от имен переменных и других объектов.
Примеры: |
|
|
|
#define |
N |
10 |
// по всему тексту вместо N число10, |
#define |
PI |
3.1416926 |
// вместо PI его числовое значение, |
#define |
STR "Строковая константа, подставляется в текст\n". |
Директива #define может определить не только именованную константу, но и выполнить макроподстановки.
Примеры: |
|
|
#define |
N1 N+1 |
// вместо N1 текст 10 + 1, |
#define |
int long |
// в тексте все описания int заменятся |
|
// на long. |
Удобно использовать эту директиву, чтобы, например, ввести в употребление логические константы, которых нет в синтаксисе С:
#define |
TRUE 1 |
#define |
FALSE 0 |
Кроме того, если в замещаемом имени есть скобки, следующие за именем без пробела, то это макроопределение с параметрами, синтаксис которого:
#define имя (список_параметров) выражение Например,
#define Cube(x) x*x*x
Имя здесь играет роль имени макроопределения, а параметров может быть несколько, тогда они отделяются запятыми. Между именем и списком параметров не должно быть пробела. Такое макроопределение может использоваться как функция, хотя макроподстановки не заменяют функции, и иногда могут привести к ошибкам.
Пример: |
|
#define Cube(x) x*x*x |
// макроопределение возведения в степень |
26
#include <stdio.h> void main(void)
{
int а = 2;
printf("%d %d ", a, Cube(a));
}
1.2.8.2 Директива #include
Используется для замены в тексте путем добавления текста из других файлов в точку нахождения #include.
Синтаксис: #include "filename" #include <filename>
Механизм действия — включение текста указанного файла в текущее место в программе. Включаемые файлы называются заголовочными и содержат информацию, которая для программы глобальна.
#include "filename" осуществляет поиск файла сначала в текущем каталоге, а затем в системных каталогах. Так подключаются личные файлы программиста, содержащие произвольные тексты, например, константы, объявления или описания функций.
#include <filename> осуществляет поиск файла только в системных каталогах. Так подключаются стандартные заголовочные файлы, поставляемые в комплекте со стандартными библиотеками функций.
Каждая библиотечная функция имеет свое описание (прототип). Кроме того, в заголовочных файлах описаны многие константы, определения типов и макроподстановки. Имена стандартных заголовочных файлов, содержащих описания библиотечных функций, например, <stdio.h>, для того, чтобы использовать функции ввода и вывода, <math.h>, для того, чтобы использовать описания математических функций, и другие. Их описание можно найти в справочной системе. Следует понимать, что использование include не подключает к программе соответствующую библиотеку, а только включает в текст программы на глобальном уровне все нужные описания и объявления. Сами библиотечные функции подключаются к программе в виде объектного кода на этапе компоновки, когда компоновщик обрабатывает все вызовы функций, определяет, какие потребуются программе, и собирает исполнимый файл, включая в него объектные (компилированные) коды только тех функций, к которым выполняется обращение.
1.2.9. Ввод и вывод данных в С. Начальные сведения
Определение. Ввести данное — означает присвоить произвольное значение переменной во время выполнения программы.
27
Определение. Вывести данное — означает напечатать на экране значение переменной при выполнении программы.
Простейший из способов ввода и вывода (обмена) данных — это форматированный, с определением правил размещения данных во входномвыходном потоке. Для реализации такого обмена необходима библиотека stdio.h (standart input output library), которая подключается к программе директивой #include <stdio.h>
Для ввода значения данного с клавиатуры (с эхо повтором на экране) используется функция scanf, синтаксис которой:
scanf ("форматная строка", список_ввода);
Здесь «список ввода» — имена переменных, значения которых будут введены с клавиатуры при выполнении функции scanf. Имена переменных предваряются символом &, который является признаком адресной операции, и означает, что введенное значение пересылается по адресу, определенному именем переменной. При вводе данные отделяются пробелами, или Enter.
Для вывода значения данного на экран используется функция printf, синтаксис которой:
printf ("форматная строка", список_вывода);
Здесь «список вывода» — список имен переменных и выражений (в том числе констант), значения которых появятся на экране при выполнении функции printf.
Форматная (управляющая) строка — это строка символов внутри двойных кавычек, содержащая управляющие символы и текст. При вводе данных функция scanf читает посимвольно текст из входного потока, распознает лексемы и преобразует их в машинное представление в соответствии с признаком формата, сопоставленного переменной, ожидающей данное. При выводе функция printf берет машинное представление значения переменной, и соответственно признаку формата преобразует в текстовое представление и выводит на экран.
Число управляющих символов равно числу объектов в списке ввода-вывода. Управляющий символ имеет признак %, и одно из следующих значений:
%d – ввод-вывод целого десятичного числа |
(int), |
%u – ввод-вывод целого без знака |
(unsigned), |
%f – ввод-вывод числа с плавающей точкой |
(float и double), |
%e – ввод-вывод числа в экспоненциальной форме (double и float),
%c – ввод-вывод символа |
(char), |
%l – ввод-вывод длинного значения |
(long), |
и другие. |
|
При вводе и выводе необходимо строгое соответствие типа вводимого данного управляющему символу формата.
Пример форматированного ввода и вывода:
#include <stdio.h> void main(void)
{
int my_int;
28
float my_float;
printf("\nВведите целое и дробное число\n"); scanf ("%d", &my_int);
scanf ("%f", &my_float);
printf ("%d %f", my_int, my_float);
}
При запуске программы она выведет на экран строку — приглашение ко вводу данных, затем при выполнении каждого scanf будет ожидать ввода данных. Пользователь должен ввести требуемое количество данных, отделяя их друг от друга пробелами или нажатием клавиши Enter. При завершении ввода данные тут же будут выведены на экран самым примитивным образом. Так, если ввести целое 5 и дробное 9.9, то строка вывода будет иметь вид:
5 9.900000
Поскольку при вводе данного функция scanf находится в состоянии ожидания ввода, рекомендуется каждый ввод предварять строкой, выводящей на экран приглашение для ввода данного, в котором пользователю подробно объясняют, что и как он должен сделать, чтобы правильно ввести данные. Этот простой прием существенно улучшит интерфейс любой программы.
При выводе данных для улучшения вывода рекомендуется использовать некоторые приемы.
Управляющие символы, например: \n для перевода строки при выводе; \t для выполнения табуляции.
Произвольный текст в форматной строке для приглашения на ввод данного и для пояснений при выводе, например, функция вывода может быть записана так:
printf ("Целое = %d, Вещественное = %f\n", my_int, my_float);
Пробелы в строке текста являются значащими. Теперь, если ввести целое 5 и дробное 9.9, то строка вывода будет иметь вид:
Целое = 5, Вещественное = 9.900000 Модификаторы форматов. Они используются для оформления вывода. По
умолчанию (без модификаторов) данные выводятся в поле минимальной ширины с точностью 6 знаков после запятой, число прижимается к правому краю поля. Этим выводом можно управлять:
•ширина поля — это строка цифр, определяющая наименьший размер поля вывода (позиционирование). Если число не входит в поле, игнорируется,
•точность вывода — это две цифры, определяющие общий размер поля вывода и число знаков после запятой. Используется для вещественных чисел.
В примерах обозначим знаком пробелы, которые будут в строке вывода. printf ("Целое = %4d, Вещественное = %5.2f\n", my_int, my_float);
если ввести значения 10 и 2.3, то строка вывода будет иметь вид: 29
Целое = 10, Вещественное = 9.90 если ввести значения 19951 и 12.9999, то строка вывода будет иметь вид: Целое = 19951, Вещественное = 13.00
можно сделать вывод, что число округляется, Знак минус используется для выравнивания числа влево внутри поля вывода:
printf ("Целое = %–4d, Вещественное = %–5.2f\n", my_int, my_float);
Если ввести значения 2 и 2.36666, то строка вывода будет иметь вид: Целое = 2 , Вещественное = 2.37 если ввести значения –1999 и 12.9999, то строка вывода будет иметь вид: Целое = –1999, Вещественное = 13.00
Пример использования форматированного ввода-вывода:
#include <stdio.h> |
|
|
|
#define STR "Программа" |
// для иллюстрации вывода строк |
||
void main (void) |
|
|
|
{ |
|
|
|
// вывод целого числа 336 |
|
|
|
printf ("%d\n", |
336); |
// 336 |
|
printf ("%2d\n", |
336); |
// 336 //формат 2d игнорируется |
|
printf ("%8d\n", |
336); |
// |
336 // ширина поля 8 |
printf ("%-8d\n", 336); |
// 336 |
// прижато влево |
printf("\n"); |
|
// пропуск строки при выводе |
|
// вывод вещественного числа 12.345 |
|
||
printf ("%f\n", |
12.345); |
// 12.345000 |
|
printf ("%e\n", |
12.345); |
// 1.234500е+01 |
|
printf ("%10.1f\n", 12.345); |
// |
12.3 |
printf ("%–12.1f\n", 12.345); // 12.3 printf("\n");
// вывод строки символов по формату s
printf ("%s\n", STR); |
// Программа |
|
printf ("%12s\n", STR); |
// |
Программа |
printf ("%12.5s\n", STR); |
// |
Прогр |
printf ("%-12.5s\n", STR); |
// Прогр |
|
printf("\n"); |
|
|
}
1.2.10. Управляющие конструкции языка С
Определение. Оператор — это предложение, описывающее одно действие по обработке данных или действия программы на очередном шаге ее исполнения. Будут расмотрены следующие вопросы:
• назначение операторов,
30