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

5. Операционная система Windows.

6. Система программирования Borland С++.

7. Система программирования Microsoft Visual Studio.


Протокол согласования учЕбной программы

По изучаемой учебной дисциплине

С ДРУГИМИ ДИСЦИПЛИНАМИ СПЕЦИАЛЬНОСТИ

(дисциплины, изучение которых опирается на данную дисциплину)

Название дисциплины, с которой требуется согласование   Кафедра, обеспечиваю-щая изучение этой дисциплины Предложения об изменениях в содержании учебной программы по изучаемой дисциплине Решение, принятое кафедрой, разработавшей учебную программу (с указанием даты и номера протокола)1
       
Объектно-ориентированное программирование ПОИТ отсутствуют  
Операционные системы и системное программирование ПОИТ отсутствуют  

Зав. кафедрой (________________)

_______________________

1 При наличии предложений об изменениях в содержании учебной программы по изучаемой учебной дисциплине.


Теоретический раздел

Введение

Созданный ещё в середине прошлого века, язык программирования Си остаётся одним из наиболее популярных языков программирования в мире. Несмотря на ожесточённую конкуренцию в условиях лавинообразного развития информационных технологий, знание Си по-прежнему считается одним из признаков профессионализма. Претерпев некоторые изменения за время, прошедшее с момента его появления, он не только занял место основного языка во многих областях разработки программного обеспечения, но и стал основой для десятков новых языков программирования. Так, например, сегодня подавляющее большинство веб-сайтов в той или иной степени использует PHP. В разработке кроссплатформенных приложений значительное место занимает язык программирования Java. Технология AJAX, ставшая одной из основ Веб 2.0, одним из своих компонентов содержит JavaScript. Эти и многие другие языки в той или иной степени унаследовали синтаксис и основные концепции Си.

Си уже на протяжении многих лет остаётся основным языком системного программирования вообще и разработки операционных систем в частности. Большая часть компонентов операционных систем линейки Windows, семейства UNIX (в том числе и набирающая популярность Linux) написаны именно на этом языке, причём в основном это требовательные к скорости выполнения и размеру участки кода.

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

Точную дату появления языка Си назвать крайне затруднительно: Деннис Ритчи (Dennis Ritchie, один из создателей языка) в своей публикации «The Development of the C Language» называет 1969 – 1973 гг., отмечая при этом неразрывную связь Си с разрабатывавшейся в то же время операционной системой UNIX. Наиболее продуктивным назван 1972 г.

В конце 60-х годов XX века Кен Томпсон (Ken Thompson) из Bell Telephone Laboratories, где в то время велась разработка операционной системы Multics, задался целью создать удобную в использовании вычислительную среду, в которой предполагалось использование древовидной файловой системы, интерпретатора вводимых пользователем команд, а также общих принципов доступа к различным устройствам. Язык PL/1, широко использовавшийся в ходе разработки Multics, не удовлетворял всех запросов программистов, поэтому применялся ряд других языков, включая BCPL.

ЭВМ, с которой пришлось работать К. Томпсону начиная с 1968 г., была DEC PDP-7. Объём доступной памяти составлял 8 тысяч 18-битных слов, будучи даже по тем временам весьма небольшим. Кроме того, для этой ЭВМ не было программного обеспечения, которое могло бы быть полезным для К. Томпсона. Поэтому, желая работать с языком программирования высокого уровня, он написал оригинальную версию UNIX на ассемблере для PDP-7. Сам процесс разработки происходил на компьютере GE-635, с помощью которого генерировалась бумажная лента, а эта лента в свою очередь могла быть прочитана на PDP-7. Несмотря на все трудности К. Томпсону удалось создать примитивное ядро ОС, текстовый редактор, ассемблер, простую оболочку (интерпретатор команд) и несколько утилит, после чего новая ОС стала самодостаточной и стало возможным отказаться от использования бумажной ленты.

В 1969 г., вскоре после того, как UNIX была впервые запущена на PDP-7, Дуг МакИлрой (Doug McIlroy) создал первый язык программирования высокого уровня для новой системы – реализацию TMG, языка, который использовался для написания компиляторов. Между тем, К. Томпсон решил, что UNIX требуется язык системного программирования. Таким языком стал созданный самим К. Томпсоном язык B, который, по сути, представлял собой «BCPL, ужатый до 8 КБ памяти и профильтрованный мозгом Томпсона».

После того, как разработка языка B была завершена, он был переписан на самом себе. В ходе работы над B К. Томпсон постоянно вынужден был бороться с ограничениями в объёме доступной памяти: каждое усовершенствование языка приводило к увеличению размеров компилятора, но после того, как компилятор переписывался с использованием этого усовершенствования, удавалось уменьшить его размер. К примерам таких усовершенствований можно отнести операторы наподобие =+ (в Си этому оператору соответствует +=), ++ и -- (сохранились в Си без изменений).

Язык B также не смог в полной мере решить все задачи, которые стояли перед разработчиками: были проблемы в работе с указателями, строковыми данными и т.д. Поэтому в 1971 г. Д. Ритчи начал расширять его путём добавления символьного типа данных и поддержки новой ЭВМ – PDP-11. Результатом его работы стало создание языка, который оказался промежуточным звеном между B и Си и получил название NB («new B», новый B). Одним из важных достижений стала поддержка арифметики указателей, которая используется до сих пор, а разрешение проблемы, связанной с представлением массивов и структур, стало одним из основных событий в процессе перехода от нетипизированного языка BCPL к языку с типизацией Си, фактически завершив этот переход. По словам самого Д. Ритчи, он «решил последовать однобуквенному стилю и назвал его [новый язык] C, оставляя открытым вопрос о том, является ли это название результатом движения по алфавиту или буквам названия языка BCPL».

Дальнейшие изменения в Си производились с целью в той или иной степени упростить его и сделать более понятным. Значительное место в этом процессе отводится Алану Снайдеру (Alan Snyder). Именно ему приписывается идея введения операторов && и ||, а также, частично, идея использования препроцессора для поддержки механизма включения файлов, который существовал в BCPL и B.

К началу 1973 г. новый язык и его компилятор достигли уровня, достаточного для того, чтобы переписать ядро UNIX для PDP-11. В это же время начинается развитие языка в сторону его переносимости (portability). Здесь оказались очень кстати идеи условной компиляции и макросов, предложенные Майком Леском (Mike Lesk) и Джоном Рейзером (John Reiser). Прототипом современной стандартной библиотеки ввода-вывода стал «переносимый пакет ввода-вывода» («portable I/O package»), написанный М. Леском.

В 1978 г. Д. Ритчи и Брайан Керниган (Brian Kernighan) опубликовали книгу «The C Programming Language» («Язык программирования Си»). Несмотря на то, что в ней не затрагивались некоторые нововведения, которые вскоре получили широкое распространение, она на протяжении более чем 10 лет была руководством по языку до появления формального стандарта.

К 1982 г. стало понятно, что Си нуждается в стандартизации. Наилучшее приближение к стандарту – первая редакция K&R (книги Б. Кернигана и Д. Ритчи) – уже не соответствовала тому языку, который реально использовался: например, не было даже упоминания о типах void и enum. Кроме того, излишне акцентировалось внимание на отдельных аспектах языка. Поэтому летом 1983 г. для создания стандарта языка ANSI был сформирован комитет X3J11. Предполагалось, что созданный комитетом стандарт должен будет максимально полно отражать существующие особенности языка Си. Эта задача была успешно выполнена: единственным существенным нововведением стала необходимость указания типов формальных параметров в прототипах функций. Так, до появления стандарта объявление функции могло выглядеть так:

double sin();

По стандарту предполагался следующий вариант оформления:

double sin(double);

Незначительные нововведения, такие как модификаторы const или volatile, в целом не изменили характера языка, а стандарт стал скорее набором рекомендаций по эффективному программированию на Си, чем новым изобретением.

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

Со временем у языка Си появилось много «наследников», среди которых сравнительно молодые Concurrent C, Objective C, C* и, особенно, C++. А сам язык часто используется в качестве промежуточного во многих компиляторах.

Две наиболее характерные черты языка Си и одновременно наиболее часто критикуемые – отношения между массивами и указателями, и сходство синтаксиса описаний с синтаксисом выражений. Во всех случаях особенности языка объясняются историческими событиями. Язык Си произошёл от языка без типизации, а не возник искусственным образом.

Важной особенностью языка Си также является возможность смешивания в выражениях целочисленных данных и указателей. Как компиляторы конца 70-х гг. XX века, так и современные компиляторы, не препятствуют этому явным образом. K&R Си предполагал, что обеспечение корректной работы подобных выражений является задачей программиста, что, очевидно, было слабым местом языка программирования и основным исправлением ANSI X3J11.

Успех языка Си объясняется множеством факторов. Несомненно, важнейшим из них является успех ОС UNIX. Не менее важно и то, что Си остаётся простым и компактным языком, который легко воспринимается людьми, которые знакомы с принципами работы компьютеров и оптимизации программ. В то же время язык в достаточной степени абстрагируется от особенностей конкретной ЭВМ. В равной степени важным фактором является то, что стандартная библиотека, как, впрочем, и сам язык, с течением времени претерпела весьма незначительные изменения. Язык Си изначально позиционировался как средство создания более сложных программных средств, он удовлетворяет минимальные потребности программиста, не пытаясь обеспечить его слишком большим набором готовых алгоритмов.

1. Основные типы данных

1.1. Общие сведения

Язык программирования Си предоставляет обширные возможности для работы с различными видами данных. Данные представляют собой константы или переменные.

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

Имя переменной – это идентификатор, представляющий собой любую последовательность цифр, больших и малых букв латинского алфавита и символов подчеркивания, начинающуюся обязательно буквой или символом подчеркивания. Длина идентификатора может быть произвольной, но значимыми являются первые 32 символа.

Типы данных в Си можно условно разделить на определенные группы:

1) тип void;

2) тип «функция»;

3) скалярные типы (арифметические типы, перечисления, указатели);

4) агрегированные или составные типы (массивы, структуры, объединения).

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

Для того чтобы выбранный тип данных наиболее точно соответствовал его назначению в программе, применяют модификаторы типа. В Си имеется четыре вида модификаторов: signed (знаковый), unsigned (беззнаковый), long (длинный) и short (короткий). Чтобы их использовать, достаточно перед названием типа написать любой модификатор, например, так: long int.

Базовый тип int может быть изменен любым из этих модификаторов, тип char – только модификаторами signed и unsigned, а тип double – лишь модификатором long. Модификатор signed означает, что числа будет со знаком. Если в знаковом бите содержится 0, то число положительное, если 1 – то отрицательное. Модификатор unsigned означает, что старший бит числа будет использоваться не для хранения его знака, а для расширения его модульной части. В результате максимальное значение, представимое в переменной данного типа, увеличивается вдвое.

1.2. Данные типа int

Данные типа int хранят целые числа. Представление этого типа в памяти можно символически представить так:

Видно, что старший бит – знаковый, таким образом, диапазон значений (при размере int в 2 байта) будет -215.. +215-1 (-32768..+32767). По умолчанию тип int воспринимается как signed int.

При использовании же модификатора unsigned все биты будут использованы для хранения числа:

Соответственно диапазон значений возрастает: от 0 до 216-1 (0..65535).

При применении к типу int модификатора long

Диапазон допустимых значений: -231 … +231-1

В случае, когда используется unsigned long

Диапазон допустимых значений: 0 … +232-1

Пример объявления целых переменных:

int a, b;

signed long int c;

В языке Си можно использовать как десятичные, так и шестнадцатеричные и восьмеричные целые константы. Признаком восьмеричной константы в программе на языке Си является цифра 0, предшествующая значению числа в восьмеричной записи, например, 0456. Для записи шестнадцатеричной константы надо записать число, обязательно начинающееся со знаков 0x или 0X, за которым следует целое число, представленное в шестнадцатеричной системе исчисления, например, 0x15А. Если в записи константы встречается суффикс L или l, то константа интерпретируется как long. Если суффикс U или u, то, как беззнаковая, например 0x15ALU.

1.3. Данные типа char

Данные типа char занимают в памяти 1 байт. По стандарту ASCII код от 0 до 255 в этом байте задает один из 256 возможных символов. Первым 128 значениям (коды символов от 0 до 127) соответствуют управляющие символы (от 0 до 31), строчные и заглавные буквы латинского алфавита, цифры, а также пунктуационные знаки, знаки элементарных арифметических операций и т.п. Остальные 128 значений могут занимать буквы другого языка, например, русского. По умолчанию тип char воспринимается как unsigned char.

Наиболее часто используемыми управляющими символами являются символ конца строки ‘\0’, автоматически помещаемый в конце строки и символ перехода на новую строку ‘\n’.

Константой типа char является символ, заключенный в одинарные кавычки, например: ‘а’, ‘5’. Значением символьной константы является целое число (код символа), поэтому над символьной константой определены как операции сравнения, так и все остальные операции, определенные над целыми числами.

Пример объявления переменных типа char:

char bukva = ‘a’;

signed char s;

Коды цифр и латинских букв идут в порядке возрастания, т.е.

’0’<’1’ <’2’<…<’9’<…<’A’<’B’<’C’<…<’Z’<’a’<’b’<’c’<…<’z’

При работе с таблицей ASCII надо помнить, что существует два различных представления символа в таблице: десятичный номер по порядку (от начала таблицы) и шестнадцатеричное число, показывающее, на пересечении какой строки и столбца в таблице находится символ. Например, символ ‘0’ будет иметь в программе на Си целочисленное значение типа int, равное его порядковому номеру в таблице, т.е. 48. В то же время, представление символа ‘0’ является шестнадцатеричным двухразрядным числом, которое складывается из значения строки (старший разряд числа) – ‘3’, и значения столбца (младший разряд числа) – ‘0’ таблицы ASCII. Таким образом, символ ‘0’ представляется как 0x30, т.е. число 30 в шестнадцатеричной системе счисления или же число 48 в десятичной системе счисления.

Первые 128 значений таблицы кодов ASCII:

1.4. Модификаторы доступа const и volatile

Язык Си предлагает программисту два модификатора доступа при объявлении переменных: const и volatile. Они помогают определить переменные, которые не должны изменяться в ходе выполнения программы (const), и переменные, способные при выполнении программы изменять своё значение (volatile). Использование модификатора const помогает избегать трудноуловимых ошибок программирования и повышать читабельность исходного кода программы.

Например, если во многих местах программы используется одна и та же константа (например, вещественное число с большим количеством знаков после запятой), то при многократном наборе её значения с клавиатуры высока вероятность допустить опечатку. Если же объявить для хранения этого значения переменную с модификатором const и в дальнейшем обращаться к константе через эту переменную, то ошибка в наборе имени переменной будет обнаружена на этапе компиляции.

Поскольку переменная с модификатором const не может изменяться в ходе выполнения программы, её значение задаётся один раз, при её объявлении:

const int maxCount = 32767, step = 70;

const long double pi = 3.1415926535897932384626433832;

В остальном поведение переменной, объявленной с модификатором const, ничем не отличается от поведения обычной переменной.

Модификатор volatile даёт указание компилятору не делать каких-либо предположений относительно будущих значений переменной. Он означает, что переменная может изменяться не только текущей программой, но и другими процессами. Объявляются такие переменные аналогично:

volatile unsigned int timer;

Переменные, объявленные с помощью модификатора volatile, могут эффективно использоваться при разработке программ, работающих с аппаратным обеспечением компьютера, или системных утилит.

1.5. Данные вещественного типа
(с плавающей точкой)

Для объявления данных вещественного типа используются ключевые слова float (4 байта), double (8 байт) и long double (10 байт). Признаком вещественной константы является наличие точки в записи константы или запись числа в экспоненциальной форме:

float a, b = 1.3; //Запись в форме с точкой

double c = 10e30, d = 2e12; //Запись в экспоненциальной форме

При записи в экспоненциальной форме первой записывается мантисса числа, затем символ ‘e’ или ‘E’ и следующий за ним показатель степени числа 10.

В таблице 1.1 приведены основные типы данных, используемые в языке Си.

Таблица 1.1. Основные типы данных, используемые в языке Си

Тип Размер в битах Диапазон значений
char   от 0 до 255
unsigned char   от 0 до 255
signed char   от -128 до 127
int   от -32768 до 32767
unsigned int   от 0 до 65535
signed int   то же, что int
short int   от -32768 до 32767
unsigned short int   от 0 до 65535
signed short int   то же, что short int
long int   от -2 147 483 648 до 2 147 483 647
signed long int   то же, что и long int
unsigned long int   от 0 до 4 294 967 295
float   от 1.17Е-38 до 3.37Е+38 точность – не менее 7 значащих десятичных цифр
double   от 2.23Е-308 до 1.67Е+308 точность – не менее 15 значащих десятичных цифр
long double   от 3.37Е-4932 до 1.2Е+4932 точность – не менее 19 значащих десятичных цифр

1.6. Элементарный ввод-вывод

Для вывода данных на экран монитора можно использовать стандартные функции printf, puts и putchar.

printf(“строка формата”, параметр_1, …, параметр_n);

Функция printf выводит форматированные данные на экран. Для форматирования вывода используется символьная строка формата. Если функция находит в строке формата символ ‘%’ с указанным типом формата, то соответствующий параметр функции преобразуется при выводе в указанный формат (таблица 1.2).

Таблица 1.2. Типы формата

Тип формата Представление данных при выводе
d Целое десятичное число
c Отдельный символ
s Символьная строка
f Число с плавающей запятой в записи с фиксированной десятичной точкой
e Значение со знаком в экспоненциальной форме (с символом e или E)

puts(string)– вывод строки с переводом курсора на новую строку.

В программе для вывода текстовой строки желательнее использовать puts вместо printf, так как функция puts занимает меньше памяти и работает при выводе строк быстрее, чем при аналогичном использовании printf.

putchar(char) – вывод символа. Если вы хотите непосредственно вывести какой-либо символ, не прибегая к помощи переменных или констант символьного типа, то можно использовать такую форму записи функции:

Например, putchar(‘g’) выведет на экран латинскую букву ‘g’.

Пример использования функций вывода:

#include <stdio.h>

void main()

{

char symbol = ‘С’;

int x = 10, y = -3;

float a = 34.23;

putchar(symbol);

putchar(‘\n’);

puts(“abc”);

printf(“Целые числа со знаком: %d %i \n”, x, y);

printf(“Число типа float: %f\n”, a);

}

В результате выполнения данной программы на экран будет выведено:

С

abc

Целые числа со знаком: 10 -3

Число типа float: 34.230000

Для ввода данных с клавиатуры можно использовать стандартные процедуры scanf, gets и getchar.

scanf(“строка формата”, &параметр_1, …, &параметр_n);

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

сhar symbol;

int x, y;

scanf(“%d”, x); //Ошибка – перед x отсутствует “&”

scanf(“%d”, &y); //Верно

scanf(“%c”, &symbol); //Верно

Кроме того, чтение строки при использовании scanf производится только до первого встреченного пробела, символа табуляции или символа конца строки.

gets(string) – считывание строки с клавиатуры. Чтение строки функцией gets происходит до первого встреченного символа конца строки. Этот символ автоматически заменяется нуль-символом ‘\0’.

getchar() – считывание символа с клавиатуры.

Рассмотрим пример использования функций ввода:

#include <stdio.h>

void main()

{

char symbol;

int x;

scanf("%d", &x);

symbol = getchar();

printf("%d %c", x, symbol);

}

Результатом выполнения данной программы является вывод на экран считанного ранее с клавиатуры целого числа x и символа symbol.

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

Программа на языке Си состоит из одной или нескольких функций, причем одна из них обязательно должна иметь имя main. При запуске программы система передает управление функции main, т.е. именно с main начинается выполнение всей программы.

Структуру простой программы на языке Си можно представить таким образом:

#include <stdio.h> //Подключение библиотеки функций

//стандартного ввода/вывода

void main()

{

Объявления данных

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

}

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

Пример простой программы на языке Си:

#include <stdio.h>

main()

{

//Переменные, используемые в функции main

int i=2, j=3, k;

//Вычисления

k = i+j;

//Вывод результатов

printf(“Результат i+j равен: %d”, k);

}

В результате выполнения программы на экране появится:

Результат i+j равен: 5

В первой строке программы стоит команда #include, в соответствии с которой к программе будет подсоединен раздел системной библиотеки, содержащий прототипы функций ввода/вывода на стандартные устройства – stdio.h. Далее объявляются переменные k, i и j, происходит вычисление выражения k=i+j. Функция printf выводит на экран значение переменной k.

2. Операции и выражения

2.1. Выражение и его интерпретация

Выражение в языке Си – это последовательность операндов, операций и символов-разделителей. Операнды – это переменные, константы либо другие выражения. Разделителями в Си являются символы

[] () {},;: … * = #,

каждый из которых выполняет свою функцию. Выражение может состоять из одной или нескольких операций и определять выполнение целого ряда элементарных шагов по преобразованию информации. Компилятор соблюдает строгий порядок интерпретации выражений, называемый правилами предшествования. Этот порядок может быть изменён, если отдельные части выражений заключить в круглые скобки. Элементарная операция по преобразованию информации задаётся знаком операции.

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

2.2. Основные операции

2.2.1. Арифметические операции

В таблице 2.1 перечислены допустимые арифметические операции языка Си. Операции +, –, * и / работают в Си точно так же, как и в большинстве других языков. Их можно применять практически ко всем встроенным типам данных. Когда / применяется к целому числу или символу, остаток отбрасывается, например: 10 / 3 равно 3.

Операция взятия по модулю % выдает остаток от целочисленного деления. Операция % не может использоваться с типами float и double. Следующий фрагмент демонстрирует его использование:

int x, у;

х = 10;

y = 3;

printf ("%d", x/y); // выводит 3

printf("%d", х%у); // выводит 1 – остаток

// целочисленного деления

х = 1;

y = 2;

printf("%d %d", x/y, x%y); // выводит 0 и 1

Последняя строка печатает 0 и 1, т.к. в результате целочисленного деления 1/2 получается 0, с остатком 1. 1%2 выдает остаток 1.

Унарный минус фактически умножает одиночный операнд на –1, то есть число, перед которым стоит знак минус, меняет свой знак.

Таблица 2.1. Арифметические операции

Операция Действие
– + * / % –– ++ Вычитание, а также унарный минус Сложение Умножение Деление Взятие по модулю (остаток) Инкремент Декремент

Язык Си предоставляет две полезные операции, обычно отсутствующие в других языках. Это операции инкремента и декремента, ++ и ––. Операция ++ добавляет 1 к операнду, а –– вычитает 1. Поэтому следующие операции эквивалентны:

x = х + 1;

это то же самое, что и

++x;

Аналогично

x = x – 1;

это то же самое, что и

––x;

Как операция инкремента, так и операция декремента могут стоять перед операндом (префиксный) или после операнда (постфиксный). Например:

х = х + 1;

может быть записано как

++x;

или

х++;

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

х = 10;

у = ++x;

В этом случае у устанавливается в значение 11. А если записать это как

х = 10;

y = x++;

то у будет иметь значение 10. В обоих случаях х устанавливается в 11.

2.2.2. Побитовые логические операции

В противоположность большинству языков, Си поддерживает все существующие побитовые операции. Поскольку Си создавался, чтобы заменить ассемблер, то была необходимость поддержки всех (или по крайней мере большинства) операций, которые может выполнить ассемблер. Побитовые операции – это сброс, установка или сдвиг битов в байте или слове, которые соответствуют стандартным типам языка Си char и int. Побитовые операции не могут использоваться с float, double, long double, void и агрегированными типами. Таблица 2.2 содержит имеющиеся побитовые операции.

Побитовые операции И, ИЛИ, НЕ используют ту же таблицу истинности, что и их логические эквиваленты, за тем исключением, что они работают побитно.

Таблица 2.2. Побитовые операции

Операция Действие
& | ^ ~ >> << И ИЛИ Исключающее ИЛИ Побитовая инверсия Сдвиг вправо Сдвиг влево

Побитовое логическое И имеет следующую таблицу истинности:

P Q P&Q
     
     
     
     

Побитовое логическое ИЛИ имеет следующую таблицу истинности:

P Q P|Q
     
     
     
     

Исключающее ИЛИ имеет следующую таблицу истинности:

P Q P^Q
     
     
     
     

Побитовая инверсия имеет следующую таблицу истинности:

P ~P
   
   

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

Побитовое И чаще всего используется для выключения битов. То есть любой бит, установленный в 0, вызывает установку соответствующего бита в другом операнде также в 0. Например, 7&2=2:

&───── число 7 в двоичном представлении число 2 в двоичном представлении выполнение побитового И результатом является число 2

Побитовое ИЛИ, в противоположность И, может использоваться для установки битов. Например, в результате операции 5 | 2 получаем:

| ───── число 5 в двоичном представлении число 2 в двоичном представлении побитовое ИЛИ результат равен 7

Исключающее ИЛИ или XOR устанавливает бит, если соответствующие биты в операндах отличаются. Например, в результате операции 7 ^ 2 получаем:

^ ───── 7 в двоичном представлении 2 в двоичном предтавлении побитовое исключающее ИЛИ результат равен 5

Операция побитовой инверсии ~ инвертирует состояние каждого бита операнда, то есть 1 устанавливается в 0, а 0 – в 1.

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

Исходный байт После первого дополнения После второго дополнения 11010011 (зашифрованный результат) 00101100 (получен исходный байт)

2.2.3. Операции сдвига

Операции сдвига >> и << сдвигают биты в переменной соответственно вправо и влево на указанное число двоичных разрядов. Общий вид операции сдвига вправо (в сторону младших разрядов переменной):

переменная >> число сдвигов

а общий вид операции сдвига влево (в сторону старших разрядов переменной):

переменная << число сдвигов

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

Операции битового сдвига могут также использоваться для выполнения быстрого умножения и деления целых чисел. Сдвиг влево на 1 бит равносилен умножению на 2, а сдвиг вправо – делению на 2.

int x = 7;

...

x = x << 2; // x будет равно 28

2.2.4. Операция присваивания

Общий вид операции присваивания следующий:

имя_переменной = выражение;

где выражение может быть как простой одиночной константой, так и сложным выражением. Как в Бейсике и Фортране, в Си используется знак равенства для отображения операции присваивания (не так, как в Паскале или Модуле-2, где используется составной символ:=). В левой части операции присваивания должна использоваться ячейка памяти, определяемая или переменной, или элементом массива, или разыменованным указателем. Не допускаются имена функций и констант. При нарушении данного правила (например, если в левой части операции стоит имя функции или константы) компилятор Borland C++ 3.1 сообщает об ошибке «Lvalue required».

Язык Си позволяет присваивать нескольким переменным одни и те же значения путем использования многочисленных присваиваний в одной операции. Например, данный фрагмент программы присваивает переменным х, у и z значение 0:

х = у = z = 0;

Язык Си имеет несколько специальных сокращенных операций, кодирующих некоторые операции присваивания. Например:

х = х + 10;

может быть кратко записано как

х += 10;

Операция += сообщает компилятору, что необходимо присвоить переменной х старое значение х плюс 10.

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

переменная = переменная оператор выражение;

то же самое, что и

переменная оператор = выражение;

В другом примере

х = х – 100;

записывается как

х –= 100;

2.2.5. Операция sizeof

sizeof – это унарная операция, возвращающая длину в байтах переменной или типа, помещенных в скобки. Например:

float f;

printf(“%d”, sizeof f);

printf("\n%d", sizeof(int));

выдает

Использование sizeof помогает создавать переносимый код для тех случаев, когда код зависит от размера стандартных типов данных Си.

2.2.6. Преобразование типов в выражениях

Когда переменные и константы различных типов смешиваются в выражениях, то происходит преобразование к одному типу. Компилятор преобразует все операнды к «старшему» типу. «Старшим» называется тип, диапазон представления у которого больше.

Последовательность типов, начиная с «младшего», и заканчивая «старшим», можно представить в следующем виде:

char -> int -> unsigned int -> long -> unsigned long -> float -> double -> long double.

Например, если один из операндов выражения имеет тип int, а другой – «старший» тип float, то всё выражение будет иметь тип float.

Рассмотрим пример преобразования типов.

Сначала символ ch преобразуется к типу int, а вещественная переменная f преобразуется к double. Затем выражение ch / i преобразуется к старшему типу double, поскольку выражение f * d имеет тип double. Конечный результат имеет тип double.

char ch;

int i;

float f;

double d;

result = (ch / i) + (f * d) – (f + i);

│ │ │ │ │ │

char int float double float int

└─┬──┘ └─┬─┘ └─┬─┘

int double float

└────┬─────────┘ │

double │

└───────────┬───────────┘

double

2.2.7. Операция преобразования типов

Имеется возможность заставить выражение принять определенный тип с помощью операции преобразования типов. Эта операция имеет следующий вид:

(тип) выражение

где тип – это один из стандартных типов данных Си или определённый пользователем тип. Например, если необходимо, чтобы выражение х / 2 имело тип float (остаток сохранится), следует написать:

(float) x / 2

Операция преобразования типов – это унарный оператор.

Хотя принудительные преобразования редко используются при программировании на Си, бывают случаи, когда они просто необходимы. Например, необходимо использовать целое число для управления циклом и, кроме этого, требуется вычислять дробную часть от деления этого числа на какое-либо другое, как в следующем примере:

#include <stdio.h>

// выводит i и i/2 с дробной частью

void main()

{

int i;

for (i = 1; i <= 100; ++i)

printf(“%d / 2 is: %f\n”, i, (float) i/2);

return 0;

}

Без принудительного преобразования (float) будет вычисляться только целая часть, а благодаря (float) получим также и дробную часть.

2.2.8. Приоритеты в языке Си

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

Таблица 2.3. содержит перечень всех операций языка Си, упорядоченных в порядке убывания приоритета (горизонтальная черта отделяет операции с одинаковым приоритетом). В пределах своего приоритета операции выполняются справа налево или слева направо. Например, выражение 10 / 2 * 4 будет равно 20, т. к. сначала выполняется операция деления, затем операция умножения.

Таблица 2.3. Приоритеты операций

Название Символ операции Порядок выполнения
Обращение к функции Выделение элемента массива Выделение поля структурной переменной Выделение поля структурной переменной по указателю на её начало () [] . –> Слева направо
Логическое отрицание Поразрядное логическое НЕ Изменение знака (унарный минус) Инкремент Декремент Определение адреса переменной Обращение к памяти по значению указателя Преобразование к типу Определение размера в байтах ! ~ – ++ –– & * (type) sizeof Справа налево
Умножение Деление Определение остатка целого деления * / % Слева направо
Сложение Вычитание + – Слева направо
Сдвиг влево Сдвиг вправо << >> Слева направо
Меньше Меньше или равно Больше Больше или равно < <= > >= Слева направо
Равно Не равно == != Слева направо
Поразрядное логическое И & Слева направо
Поразрядное исключающее ИЛИ ^ Слева направо
Поразрядное логическое ИЛИ | Слева направо
Логическое И && Слева направо
Логическое ИЛИ || Слева направо
Операция условия ?: Справа налево
Присваивание = += –= *= /= %= <<= >>= |= ^= &= Справа налево
Операция «запятая» , Слева направо

Заметим, что приоритеты побитовых операторов &, ^ и | ниже, чем приоритет == и!=, из-за чего в побитовых проверках типа

if ((x & y) == 0),

чтобы получить правильный результат сравнения x & y с 0, необходимо использовать скобки. Использование дополнительных круглых скобок не вызовет ошибок и не уменьшит скорость вычисления выражения.

if ((a = b) == с)

В этом примере сначала значение b присваивается операнду а, затем значение с сравнивается с полученным значением операнда а.

Можно использовать круглые скобки для уточнения порядка вычисления. Например: какое из двух выражений легче читать?

x=y/3-34*temp&127;

или

x = (у / 3) – ((34 * temp) & 127);

3. Операторы управления вычислительным процессом

3.1. Оператор if

Оператор if имеет следующую полную форму записи:

if (выражение)

оператор_1;

else

оператор_2;

Выражение – это некоторое условие, которое возвращает одно из двух значений: ноль («ложь»), не ноль – («истина»).

Данный оператор предоставляет программисту возможность выполнить один из двух участков вычислений в зависимости от того, является выражение истинным или ложным (рис.3.1).

Рис.3.1. Схема работы оператора if...else

Например,

if (a>=b)

printf(“Число a не меньше числа b.\n”);

else

printf (“Число a меньше числа b.\n”);

Схема на рисунке 3.1 иллюстрирует работу оператора if/else. Если выражение дает результат «истина», то выполняется оператор 1, иначе оператор 2. Проследим выполнение нашего примера программы: если компилятор определит истинность выражения, то на экран выведется сообщение «Число a не меньше числа b.». Если условие будет ложным, то на экране появится - «Число a меньше числа b.».

Если операторная часть выражения оператора if или else содержит не один оператор, а несколько, то необходимо заключать их в фигурные скобки.

if (выражение)

{

оператор_1;

оператор_2;

}

else

оператор_3;

В данном случае если выражение будет истинным, то выполнятся оператор 1 и оператор 2, иначе выполнится оператор 3.

Существует сокращенная форма оператора if без ветви else (рис.3.2):

if (выражение)

оператор;

Рис.3.2. Схема работы оператора if без ветви else

Например,

if (a>=b)

printf(“Число a не меньше числа b.\n”);

Если в результате проверки выражения возвращается «истина», то оператор выполняется, после чего выполнение передается следующей строке программы; если же результатом проверки будет «ложь», то выполнение оператора будет пропущено. Т.е. в нашем примере компилятор определит, является ли выражение истинным «число a больше или равно числу b», и если данное выражение истинно, то на экране появится сообщение «Число a не меньше числа b». Если выражение ложно, то на экран ничего не выведется.

Для наглядного иллюстрирования работы данного оператора if с единичным выбором на рис.3.2 изображена схема его работы.

Вложенные операторы if/else служат для проверки составных условий, при этом одни операторы if/else помещаются внутрь других операторов if/else. Если использовать несколько вложенных операторов if, то следует тщательно следить, какому из них соответствует ветвь else.

if (выражение 1)

оператор 1;

else

if (выражение 2)

оператор 2;

else

if (выражение 3)

оператор 3;

else

оператор 4;

Схема на рисунке 3.3 демонстрирует работу вложенного оператора if/else. При выполнении программы компилятор будет проверять выражение за выражением до тех пор, пока одно из них не возвратит условие «истина». Как только это произойдет, все остальные операторы else и связанные с ними действия будут пропущены. В том случае, если ни одно из выражений не будет истинным, то ни один из операторов не будет выполнен, кроме оператора 4.

Рис.3.3. Схема работы вложенного оператора if/else

3.2. Операции отношения

Операции отношения (сравнения) и логические операции используются для формирования логических выражений, имеющих только два значения: 0 – если выражение «ложь» и не ноль, если выражение «истина».

Язык Си поддерживает следующие операции отношения (таблица 3.1), которые условно можно разделить на 2 группы в зависимости от их взаимного приоритета:

<, <=, >, >= (более высокий приоритет)

==,!= (более низкий приоритет)

Таблица 3.1. Представление операций отношения и равенства

Операция Выражение Что обозначает
> >= < <= == != a>b a>=b a<b a<=b a==b a!=b a больше b a больше или равно b a меньше b a меньше или равно b a равно b a не равно b

Однако следует отметить, что все данные операции выполняются слева направо.

Как операторы отношения, так и логические операторы имеют более низкий приоритет по сравнению с арифметическими операторами.

Например, выражение 15>10+9 в результате даст результат «ложь», так как данное выражение будет вычисляться как 15>(10+9).

Распространенными ошибками при работе с операциями отношения являются:

a) использование операции присваивания = вместо операции равно ==

b) разделение символов в операциях пробелами >= вместо >=

c) написание символа в операции в обратном порядке =! вместо!=

3.3. Логические операции

Рассматривая операции отношения, мы проверяли одно простое выражение (некоторое условие). В языке Си существуют логические операции, с помощью которых можно формировать сложные условие путем объединения более простых. Логические операции всегда дают значение 0 – «ложь» или 1 –«истина».

Логические операции
Операция Действие
&& || ! И ИЛИ НЕ

Например, мы хотим выполнить оператор или блок операторов только если два простых выражения истинны одновременно. Здесь можно применить логическую операцию &&:

if (a>=60 && a<=130)

printf(“Число а находится в диапазоне от 60 до 130”);

Во фрагменте кода программы оператор if содержит два простых выражения. Сначала оцениваются два простых выражения, так как приоритет операций <= и >= выше, чем операции &&. Затем рассматривается объединенное выражение. Это выражение является истинным только тогда, когда оба простых выражения истинны. И если это так, то на экран выведется текст “Число а находится в диапазоне от 60 до 130”. Если же оба или хотя бы одно из простых выражений ложно, то программа игнорирует вывод и переходит к оператору, следующему за if. В таблице 3.2 приводится таблица истинности операции &&.


Таблица 3.2. Таблица истинности операции &&

Выражение 1 Выражение 2 Выражение 1 && Выражение 2
     
     
     
     

Рассмотрим теперь операцию || (логическое ИЛИ). Допустим, что мы хотим обеспечить истинность хотя бы одного из простых выражений. Например,

if (а <= 10 || а >=100)

printf(“Число а попадает в один из интервалов”);

Этот оператор if также состоит из двух простых выражений, вычисляемых в первую очередь. Затем рассматривается объединенное выражение. В результате сообщение «Число а попадает в один из интервалов» выводится, если оба или одно из простых выражений истинно. В таблице 3.3 приводится таблица истинности операции ||.

Таблица 3.3. Таблица истинности операции ||

Выражение 1 Выражение 2 Выражение 1 || Выражение 2
     
     
     
     

Операция! (логическое отрицание) имеет в качестве операнда только одно выражение. Данная операция помещается перед выражением, когда мы хотим выбрать ветвь программы с ложным первоначальным условием.

if (!(a == b))

printf(“Неверно, что число a равно числу b.\n”);

Заключение условия a == b в скобки производится, так как логическая операция отрицания имеет более высокий приоритет, чем операция равенства.


Таблица 3.4. Таблица истинности операции!

Выражение !Выражение
   
   

3.4. Операция запятая

Запятая может быть использована в качестве операции, а может применяться как разделитель.

Несколько выражений, разделенных запятыми, вычисляются слева направо. Например, выражение (x=3, 3*x) будет иметь результат 9, однако переменная х примет значение 3.

Операция, (запятая) позволяет вводить составные выражения и в операторах цикла for, при этом первым будет выполняться выражение, стоящее слева, затем следующее.

for (i=0, sum=0; i<=100; i++)

Рассмотрим использование запятой в качестве разделителя.

· разделение начальных значений элементов массивов и компонентов структур при их инициализации.

int Massiv[5] = {1, 2, 3, 4, 5};

· использование в списках формальных и фактических параметров при работе с функциями.

void print(char value, int x);

· использование для описания определенных объектов (например, переменных) одного типа

int x, y;

float a, b, c, f1, f2;

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

int i = 1;

int m[3] = {i, (i = 2, i * i), i};

В данном примере запятая в круглых скобках выступает в роли знака операции. Операция «=» имеет более высокий приоритет, чем операция «запятая». Поэтому вначале i получает значение 2, затем вычисляется произведение i * i, и этот результат служит значением выражения в скобках. Однако значением переменной i остается 2. Значения m[0], m[1], m[2] будут соответственно 1, 4, 2.

В приведённом примере видна ещё одна особенность операции запятая: выражения, являющиеся «аргументами» этой операции, вычисляются в порядке их записи независимо от приоритетов операций. Так, в приведённом примере наивысший приоритет – у операции умножения, но оно выполняется только после операции присваивания, имеющей более низкий приоритет.

3.5. Операция условия?:

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

выражение? оператор 1: оператор 2;

(a>=b)? printf(“Число a не меньше числа b.\n ”):

printf(“Число a меньше числа b.\n ”);

В данном примере, если число a >= числа b, условие «истина», то на экране появится – «Число a не меньше числа b.», иначе «Число a меньше числа b.».

3.6. Оператор безусловного перехода goto

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

goto метка;

Метка представляет собой некоторый идентификатор, после которого следует двоеточие. Метка не должна выходить за пределы той функции, где находится оператор goto, который на нее ссылается.

void main()

{

int i=1;

start:

if (i>5)

goto finish;

printf(" %d ", i++);

goto start;

finish:

putchar('\n');

return 0;

}

Результат работы программы: 1 2 3 4 5

Приведенный программный код показывает работу оператора goto. Цикл, основанный на операторе goto, выполняется пять раз, и при этом каждый раз выводится значение счетчика. После инициализации переменной i значением 1 программа проверяет ее, определяя, не больше ли она 5 (метка start пропускается, так как она не выполняет никаких действий). Если значение переменной больше 5, то управление передается первому оператору, расположенному за меткой finish. Если же значение меньше или равно 5, то выполняется тело цикла (состоящее из вызова функции printf), после чего управление передается первому оператору после метки start, т.е. в данном случае – оператору if.

При разработке программ оператор goto использовать не рекомендуется, так как при его наличии программы становятся неструктурированными, их сложнее отлаживать и модифицировать.

3.7. Оператор switch

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

Оператор break по своей сути аналогичен goto, но в отличие от последнего, не позволяет выполнять произвольных переходов, ограничивая их точкой окончания языковой конструкции, внутри которой он применяется. С точки зрения программной логики работы оператора switch оператор break обязательно должен завершать группу операторов.

switch (целое выражение)

{

case константа_1: операторы_1; break;

case константа_2: операторы_2; break;

...

case константа_n: операторы_n; break;

default: операторы_n+1;

}

Сначала вычисляется целое выражение, и результат сравнивается со значениями константа_1, константа_2,..., константа_n. Если где-то будет совпадение, то выполняется группа операторов, имеющих соответствующую метку. Если же значение целого выражения не совпало ни с одной из констант, то выполняется группа операторов, помеченная ключевым словом default. При отсутствии ключевого слова default и несовпадении значения выражения ни с одной из констант, операторы в конструкции switch не выполняются.

Пример.

char c='a';

clrscr();

switch (c)

{

case 'm': printf("c \n"); break;

case 'h': printf("c \n"); break;

case 'e': printf("c \n"); break;

case 'a': printf("c \n"); break;

case 't': printf("c \n"); break;

case 'z': printf("c \n"); break;

case 'v': printf("c \n"); break;

case 'b': printf("c \n"); break;

default: printf("The end \n");

}

Вышеприведенный фрагмент кода программы и схема на рисунке 3.4 иллюстрируют работу оператора switch. Если выражение совпадет с константой (case ‘a’ совпадает с выражением), то на экран выведется сообщение «с», как в данном примере. Если же совпадения не последует, то по умолчанию на экране появится «The end».

Рис.3.4. Схема работы оператора switch

3.8. Операторы цикла

3.8.1. Оператор for

Язык Си имеет несколько операторов для организации циклов. Отличие цикла for от циклов while и do..while состоит в том, что в нем, как правило, число повторений заранее известно, т.е. можно удобно задавать начальные значения и изменять параметры цикла.

Цикл for (цикл с параметром) обычно используется в тех случаях, когда нужно точно определить необходимое количество повторений.

for (выражение 1; выражение 2; выражение 3) оператор;

выражение 1 – задает начальное значение параметра цикла;

выражение 2 – условие продолжение цикла (если истина – продолжение, если ложь – выход из цикла);

выражение 3 – модификатор счетчика повторений цикла.

Пример.

int i;

for (i=0; i<=10; i++)

printf(“%d \n”,i);

Данный фрагмент программы и схема на рисунке 3.5 иллюстрируют работу оператора for. Когда программа в своем выполнении доходит до цикла for, первым выполняется инициализирующее выражение, в котором обычно устанавливается счетчик цикла. Затем условное выражение, которое также называется условием прекращения цикла; пока оно равно истине, цикл не прекращается. Каждый раз после всех строк тела цикла выполняется модифицирующее выражение, в котором происходит изменение счетчика. Как только условное выражение даст ложь, все строки тела цикла будут пропущены и управление будет передано следующей за циклом команде (т.е. выход из цикла).

<

Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: