История возникновения языка СИЯзык программирования Си был разработан в лабораториях Bell Labs в период с 1969 по 1973 годы. Согласно Ритчи, самый активный период творчества пришёлся на 1972 год. Язык назвали «Си» (C — третья буква латинского алфавита), потому что многие его особенности берут начало от старого языка «Би» (B — вторая буква латинского алфавита). Существует несколько различных версий происхождения названия языка Би. Кен Томпсон указывает на язык программирования BCPL, однако существует ещё и язык Bon, также созданный им, и названный так в честь его жены Бонни. Существует несколько легенд, касающихся причин разработки Си и его отношения к операционной системе UNIX, включая следующие:
Структура программы на языке СИВсе программы на языке СИ содержат директивы препроцессора, описания, определения, выражения, операторы и функции.
Структуры последовательности в программах на СИДля того, чтобы любое выражение языка воспринималось как действие, в конце выражения необходимо ставить точку с запятой. Если необходимо, чтобы с точки зрения синтаксиса несколько действий воспринималось как одно, то перед первым из действий необходимо открыть фигурную скобку, а после закрыть. Такая конструкция называется составной интсрукцией или составным оператором.Структуры развилки в программах на СИДля организации условных и безусловных переходов в программе на языке Си используются операторы: if - else, switch и goto. Первый из них записывается следующим образом: If (проверка_условия) оператор_1; else оператор_2; Оператор switch позволяет выбрать одну из нескольких альтернатив. Он записывается в следующем формальном виде: switch (выражение) { case константа_1: операторы_1; break; case константа_2: операторы_2; break; ........ ........ default: операторы_default; }Структуры повторения в программах на СИЦиклы организуются, чтобы выполнить некоторый оператор или группу операторов определенное число раз. В языке Си три оператора цикла: for, while и do - while. Первый из них формально записывается, в следующем виде: for (выражение_1; выражение_2; выражение_3) тело_цикла Тело цикла составляет либо один оператор, либо несколько операторов, заключенных в фигурные скобки { ... } (после блока точка с запятой не ставится). В выражениях 1, 2, 3 фигурирует специальная переменная, называемая управляющей. По ее значению устанавливается необходимость повторения цикла или выхода из него. В языке Си принято следующее правило. Любое выражение с операцией присваивания, заключенное в круглые скобки, имеет значение, равное присваиваемому. Например, выражение (а=7+2) имеет значение 9. После этого можно записать другое выражение, например: ((а=7+2)<10), которое в данном случае будет всегда давать истинное значение. Допускаются вложенные конструкции, т.е. в теле некоторого цикла могут встречаться другие операторы for. Оператор while формально записывается в таком виде: while (выражение) тело_цикла Выражение в скобках может принимать ненулевое (истинное) или нулевое (ложное) значение. Если оно истинно, то выполняется тело цикла и выражение вычисляется снова. Если выражение ложно, то цикл while заканчивается. Оператор do-while формально записывается следующим образом: do {тело_цикла} while (выражение); Допускается вложенность одних циклов в другие, т.е. в теле любого цикла могут появляться операторы for, while и do - while.Операторы безусловного перехода в программах на СИДля прекращения последующих проверок после успешного выбора некоторого варианта используется оператор break, обеспечивающий немедленный выход из переключателя switch. Рассмотрим правила выполнения безусловного перехода, который можно представить в следующей форме: goto метка; Метка - это любой идентификатор, после которого поставлено двоеточие. Оператор goto указывает на то, что выполнение программы необходимо продолжить начиная с оператора, перед которым записана метка. Метку можно поставить перед любым оператором в той функции, где находится соответствующий ей оператор goto. Ее не надо объявлять. Оператор "continue;" вызывает переход к следующей итерации цикла, т.е. к очередной проверке условия. Естественно, все операторы тела цикла, находящиеся между continue и концом тела цикла пропускаютсяИмена переменных и типы данных в программах на СИВ языке различают понятия "тип данных" и "модификатор типа". Тип данных - это, например, целый, а модификатор - со знаком или без знака. Целое со знаком будет иметь как положительные, так и отрицательные значения, а целое без знака - только положительные значения. В языке Си можно выделить пять базовых типов, которые задаются следующими ключевыми словами:
Арифметические операторы, операторы отношения и логические операторы в программах на СИРазличают унарные и бинарные операции. У первых из них один операнд, а у вторых - два. Начнем их рассмотрение с операций, отнесенных к первой из следующих традиционных групп: 1. Арифметические операции. 2. Логические операции и операции отношения. 3. Операции с битами. Арифметические операции задаются следующими символами (табл. 2): +, -, *, /, %. Последнюю из них нельзя применять к переменным вещественного типа. Например: a = b + c; x = y - z; r = t * v; s = k / l; p = q % w; Логические операции отношения задаются следующими символами (см. табл. 2): && ("И"), || ("ИЛИ"), ! ("НЕ"), >, >=, <, <= , = = (равно), != (не равно). Традиционно эти операции должны давать одно из двух значений: истину или ложь. В языке Си принято следующее правило: истина - это любое ненулевое значение; ложь - это нулевое значение. Выражения, использующие логические операции и операции отношения, возвращают 0 для ложного значения и 1 для истинного. Ниже приводится таблица истинности для логических операций. Примеры: если a = 0000 1111 и b = 1000 1000, то ~a = 1111 0000, a << 1 = 0001 1110, a >> 1 = 0000 0111, a & b = 0000 1000, a ^ b = 1000 0111, a | b = 1000 1111.Инкрементные и декрементные операторы в языке СИВ языке предусмотрены две нетрадиционные операции инкремента (++) и декремента (--). Они предназначены для увеличения и уменьшения на единицу значения операнда. Операции ++ и -- можно записывать как перед операндом, так и после него. В первом случае (++n или --n) значение операнда (n) изменяется перед его использованием в соответствующем выражении, а во втором (n++ или n--) - после его использования. Рассмотрим две следующие строки программы: a = b + c++; a1 = b1 + ++c1; Предположим, что b = b1 = 2, c = c1 = 4. Тогда после выполнения операций: a = 6, b = 2, c = 5, a1 = 7, b1 = 2, c1 = 5. Широкое распространение находят также выражения с еще одной нетрадиционной тернарной или условной операцией ?:. В формуле y = x ? a: b; y = a, если x не равно нулю (т.е. истинно), и y = b, если х равно нулю (ложно). Следующее выражение y = (a>b) ? a: b; позволяет присвоить переменной у значение большей переменной (а или b), т.е. y = max(a, b). Еще одним отличием языка является то, что выражение вида а = а + 5; можно записать в другой форме: a += 5;. Вместо знака + можно использовать и символы других бинарных операций (см. табл. 2).Побитовые операторы в языке СИБитовые операции можно применять к переменным, имеющим типы int, char, а также их вариантам (например, long int). Их нельзя применять к переменным типов float, double, void (или более сложных типов). Эти операции задаются следующими символами: ~ (поразрядное отрицание), << (сдвиг влево), >> (сдвиг вправо), & (поразрядное "И"), ^ (поразрядное исключающее "ИЛИ"), | (поразрядное "ИЛИ"). Примеры: если a = 0000 1111 и b = 1000 1000, то ~a = 1111 0000, a << 1 = 0001 1110, a >> 1 = 0000 0111, a & b = 0000 1000, a ^ b = 1000 0111, a | b = 1000 1111.Условное выражение. Преобразование типов.Широкое распространение находят также выражения с еще одной нетрадиционной тернарной или условной операцией ?: . В формуле y = x ? a: b; y = a, если x не равно нулю (т.е. истинно), и y = b, если х равно нулю (ложно). Следующее выражение y = (a>b) ? a: b; позволяет присвоить переменной у значение большей переменной (а или b), т.е. y = max(a, b). if (проверка_условия) оператор_1; else оператор_2; Если условие в скобках принимает истинное значение, выполняется оператор_1, если ложное - оператор_2. Если вместо одного необходимо выполнить несколько операторов, то они заключаются в фигурные скобки. В операторе if слово else может отсутствовать. В операторе if - else непосредственно после ключевых слов if и else должны следовать другие операторы. Если хотя бы один из них является оператором if, его называют вложенным. Согласно принятому в языке Си соглашению слово else всегда относится к ближайшему предшествующему ему if. Оператор switch позволяет выбрать одну из нескольких альтернатив. Он записывается в следующем формальном виде: switch (выражение) { case константа_1: операторы_1; break; case константа_2: операторы_2; break; ........ ........ default: операторы_default; } Здесь вычисляется значение целого выражения в скобках (его иногда называют селектором) и оно сравнивается со всеми константами (константными выражениями). Все константы должны быть различными. При совпадении выполнится соответствующий вариант операторов (один или несколько операторов). Вариант с ключевым словом default реализуется, если ни один другой не подошел (слово default может и отсутствовать). Если default отсутствует, а все результаты сравнения отрицательны, то ни один вариант не выполняется. Для прекращения последующих проверок после успешного выбора некоторого варианта используется оператор break, обеспечивающий немедленный выход из переключателя switch. Если в выражении появляются операнды различных типов, то они преобразуются к некоторому общему типу, при этом к каждому арифметическому операнду применяется такая последовательность правил:
В случае, если в одной бинарной операции учавствуют операнды разных типов, ещё до выполнения операции тип операнда, занимающего меньше места в памяти приводится к типу другого операнда. Такое неявное приведение типа является временным, только в пределах проводимой операции. Результат будет иметь старший тип. В операциях присваивания правила другие: значение правой части приводится к типу левой части, который и является типом рузельтата. Кроме неявного преобразования типов существует унарный оператор явного преобразования, который называется оператором приведения типа. (тип) выражение a=(float)b/c; Приоритеты и порядок вычислений в программах на СИВ табл. 2 перечислены различные операции языка Си. Их приоритеты для каждой группы одинаковы (группы выделены цветом). Чем большим преимуществом пользуется соответствующая группа операций, тем выше она расположена в таблице. Порядок выполнения операций может регулироваться с помощью круглых скобок. ( ) Вызов функции[ ] Выделение элемента массива . Выделение элемента записи -> Выделение элемента записи ! Логическое отрицание ~ Поразрядное отрицание - Изменение знака ++ Увеличение на единицу -- Уменьшение на единицу & Взятие адреса * Обращение по адресу (тип) Преобразование типа (т.е. (float) a) sizeof( ) Определение размера в байтах * Умножение / Деление % Определение остатка от деления + Сложение - Вычитание << Сдвиг влево >> Сдвиг вправо < Меньше, чем <= Меньше или равно > Больше, чем >= Больше или равно = = Равно != Не равно & Поразрядное логическое "И" ^ Поразрядное исключающее "ИЛИ" | Поразрядное логическое "ИЛИ" && Логическое "И" || Логическое "ИЛИ" ?: Условная (тернарная) операция = Присваивание +=, - =, *=, /=, %=, <<=, >>=, &=, |=, ^= Составные операции присваивания (например, а *= b (т.е. a = a * b) и т.д.) , Операция запятая Массивы. Их основные свойства. Описание массивов.Массив состоит из элементов одного и того же типа. Ко всему массиву целиком можно обращаться по имени. Кроме того, можно выбирать любой элемент массива. Для этого необходимо задать индекс, который указывает на его относительную позицию. Число элементов массива назначается при его определении и в дальнейшем не изменяется. Если массив объявлен, то к любому его элементу можно обратиться следующим образом: указать имя массива и индекс элемента в квадратных скобках. Массивы определяются так же, как и переменные: int a[100]; char b[20]; float d[50]; В первой строке объявлен массив а из 100 элементов целого типа: а[0], а[1], ..., а[99] (индексация всегда начинается с нуля). Во второй строке элементы массива b имеют тип char, а в третьей - float. Двумерный массив представляется как одномерный, элементами которого так же являются массивы. Например, определение char а[10][20]; задает такой массив. По аналогии можно установить и большее число измерений. Элементы двумерного массива хранятся по строкам, т.е. если проходить по ним в порядке их расположения в памяти, то быстрее всего изменяется самый правый индекс. Например, обращение к девятому элементу пятой строки запишется так: а[5][9]. Имя массива - это константа, которая содержит адрес его первого элемента (в данном примере а содержит адрес элемента а[0][0]). Предположим, что a = 1000. Тогда адрес элемента а[0][1] будет равен 1002 (элемент типа int занимает в памяти 2 байта), адрес следующего элемента а[0][2] - 1004 и т.д. Что же произойдет, если выбрать элемент, для которого не выделена память? К сожалению, компилятор не отслеживает данной ситуации. В результате возникнет ошибка и программа будет работать неправильно.
Понятие об указателяхУказаетль - это переменная, которая содержит адрес некоторого объекта в памяти компьютера. Понятно, что адрес - целое число. Понимание и правильное использование указателей очень важно для создания хороших программ. Многие конструкции языка Си требуют применения указателей. Например, указатели необходимы для успешного использования функций и динамического распределения памяти. С указателями следует обращаться очень осторожно. Так использование в программе неинициализированного указателя может приветси к "зависанию" компьютера. При неправильном, неаккуратном использовании указателей в программе могут возникнуть ошибки, которые очень трудно бывает обнаружить. Обойтись же без указателей в программах на язке Си нельзя. Простейшая операция над указателями - это операция &, что означает "взять адрес". Существует еще одна операция над указателями. Она обозначается символом звездочка *. Смысл этой операции таков: "значение, расположенное по указанному адресу". Хотя знак звездочка * соответствует обычной операции умножения, но никак нельзя перепутать эти две операции. Ведь арифметическая операция умножения имеет два операнда. Иначе говоря, при умножении должны быть указаны, как данные, два числа, участвующие в умножении. Поэтому и говорят, что умножение - это бинарная операция. Операция * над указателями, в отличие от арифметического умножения, - это унарная операция. То есть, другими словами, она использует всего один операнд (одно данное).Связь указателей и массивовВ языке Си имя массива - это адрес памяти, начиная с которой расположен массив, то есть адрес первого элемента массива. Если объявлен целочисленный массив int m[25] , то m является указателем на массив. Поэтому написать в программе p=m; или p=&m[0] это одно и то же, так как первая и вторая команды дают одно и то же число - адрес первого элемента массива m[25]. Для того, чтобы обратиться к 11-му элементу массива m, можно написать m[10] или *(m+10). Результат должен быть одним и тем же.Связь между функциями. Передача по значению.В программе на СИ может быть любое количество функций, среди них нет иерархий, но первой будет выполняться функция main(), где бы она, ни стояла. Любая функция в процессе своей работы может вызвать любую другую функцию или даже саму себя(рекурсия). Чтобы вызвать функцию необходимо обратиться к ней по имени в качестве оператора или в качестве операнда, указав после имени в круглых скобках список аргументов, которые необходимо передать. Если функция возвращает значение, то её можно вызвать в качестве оператора и в качестве операнда, но если вызвать её в качестве оператора, то возвращаемое значение пропадёт. If (getchar()) Если функция ничего не возвращает, то её можно вызвать только в качестве оператора. Имена переменных в списке вызова пишутся через запятую без указания типа. Такие аргументы называются фактическими параметрами. При передаче аргументов между функциями типы, количества и порядок следования фактических параметров в вызывающей функции должны соответствовать типам, количеству и порядку следования формальных параметров в вызываемой функции. Передача по значению Никакие действия, которые происходят с формальными параметрами, вызываемой функции не могут привести изменения в значение фактических параметров. Это связано с тем, что сами переменные не передаются. Компилятор передаёт копию фактических параметров и именно эти копии попадают в заданную функцию.Передача по адресу массивов и простых переменных.Если из одной функции в качестве параметра передаётся массив, то механизм передачи другой. В функцию передаётся адрес нулевого элемента массива и она фактически получает доступ ко всем элементам массива. Копирование элементов в этом случае не производится. Передача переменных по адресу используется в частности для того, чтобы позволить функции менять значения больше, чем одной переменной, переданной из одной функции и неявно возвращать эти значения в вызывающую функцию. Void top(int *w, int*e) {int c; Printf(“%d,%d\n”,*w,*e); C=*w; *w=c; Printf(“%d,%d\n”); Void main() { Int a,b; A=5; B=2; Top(&a,&b) Printf(“%d,%d”,a,b);Классы памяти.Все переменные, которые описаны внутри каких-либо фигурных скобок являются локальными. Что означает ограниченные области действия переменных при той функции или тем блоком, в котором они описаны.При временном выходе из функции такая переменная недоступна, но её значение не теряется. При окончании работы в функции переменная прекращает своё существование. Для описания таких локальных переменных может дополнительно использоваться ключевое слово “auto” и соответственно такие переменные называются автоматическими. В СИ существует 4 класса памяти.
Понятие о функциях getchar, putcharФункция getchar() получает один символ, поступающий с пульта терминала (и поэтому имеющий название), и передает его выполняюшейся в данный момент программе. Функция putchar( ) получает один символ, поступающий из программы, и пересылает его для вывода на экран. Функция getchar( ) аргументов не имеет (т. е. при ее вызове в круглые скобки не помещается никакая величина). Она просто получает очередной поступающий символ и сама возвращает его значение выполняемой программе. Например, если указанная функция получает букву Q, ее значением в данный момент будет эта буква. Функция putchar( ) имеет один аргумент. При ее вызове необходимо в скобках указать символ, который требуется вывести на печать. Аргументом может быть одиночный символ (включая знаки представляемые управляющими последовательностями, описаными в гл. 3), переменная или функция, значением которой является одиночный символ. Правильным обращением к функции putchar( ) является указание любого из этих аргументов при ее вызове. /* ввод-вывод1 */ #include main( ) { char ch; ch = getchar( ); /* строка 1 */ putchar (ch); /* строка 2 */ }Понятие о функциях printf, scanf.Функция printf() является функцией стандартного вывода. С помощью этой функции можно вывести на экран монитора строку символов, число, значение переменной... Функция printf() имеет прототип в файле stdio.h int printf(char *управляющая строка, ...); В случае успеха функция printf() возвращает число выведенных символов. Управляющая строка содержит два типа информации: символы, которые непосредственно выводятся на экран, и спецификаторы формата, определяющие, как выводить аргументы. Функция printf() это функция форматированного вывода. Это означает, что в параметрах функции необходимо указать формат данных, которые будут выводиться. Формат данных указывается спецификаторами формата. Спецификатор формата начинается с символа % за которым следует код формата. Функция scanf() - функция форматированного ввода. С её помощью вы можете вводить данные со стандартного устройства ввода (клавиатуры). Вводимыми данными могут быть целые числа, числа с плавающей запятой, символы, строки и указатели. Функция scanf() имеет следующий прототип в файле stdio.h: int scanf(char *управляющая строка); Функция возвращает число переменных которым было присвоено значение. Управляющая строка содержит три вида символов: спецификаторы формата, пробелы и другие символы. Спецификаторы формата начинаются с символа %.Обработа файлов в программах на СИ.Файлом называют способ хранения информации на физическом устройстве. Файл - это понятие, которое применимо ко всему - от файла на диске до терминала. В языке Си отсутствуют операторы для работы с файлами. Все необходимые действия выполняются с помощью функций, включенных в стандартную библиотеку. Они позволяют работать с различными устройствами, такими, как диски, принтер, коммуникационные каналы и т.д. Эти устройства сильно отличаются друг от друга. Однако файловая система преобразует их в единое абстрактное логическое устройство, называемое потоком. В Си существует два типа потоков: текстовые (text) и двоичные (binary). Текстовый поток - это последовательность символов. При передаче символов из потока на экран, часть из них не выводится (например, символ возврата каретки, перевода строки). Двоичный поток - это последовательность байтов, которые однозначно соответствуют тому, что находится на внешнем устройстве. Прежде чем читать или записывать информацию в файл, он должен быть открыт и тем самым связан с потоком. Это можно сделать с помощью библиотечной функции fopen( ). Она берет внешнее представление файла (например, c:\my_prog.txt) и связывает его с внутренним логическим именем, которое используется далее в программе. Логическое имя - это указатель на требуемый файл. Его необходимо определить; делается это, например, так: FILE *fp; Здесь FILE - имя типа, описанное в стандартном заголовочном файле stdio.h, fp - указатель на файл. Обращение к функции fopen( ) в программе осуществляется выражением: fp = fopen(спецификация файла, "способ использования файла"); Спецификация файла (т.е. имя файла и путь к нему) может, например, иметь вид: "c:\\my_prog.txt" - для файла my_prog.txt на диске с:. Способ использования файла задается следующими символами: r - открыть существующий файл для чтения; w - создать новый файл для записи (если файл с указанным именем существует, то он будет переписан); |