Структура Си-программы[]
Любая достаточно большая программа на Си состоит из файлов. Файлы транслируются Си-компилятором независимо друг от друга и затем объединяются программой-построителем задач, в результате чего создается файл с программой, готовой к выполнению. Файлы, содержащие тексты Си-программы, называются исходными.
В языке Си исходные файлы бывают двух типов:
- заголовочные, или h-файлы
- файлы реализации, или Cи-файлы
Имена заголовочных файлов имеют расширение ".h". Имена файлов реализации имеют расширения ".c".
Заголовочные файлы содержат только описания. Прежде всего, это прототипы функций. Также в h-файлах описываются имена и типы внешних переменных, константы, новые типы, структуры и т.п. При трансляции заголовочных файлов, как правило, никакие объекты не создаются.
Файлы реализации содержат тексты функций и определения глобальных переменных.
Представление исходных текстов в виде заголовочных файлов и файлов реализации необходимо для создания больших проектов, имеющих модульную структуру. Заголовочные файлы служат для передачи информации между модулями. Файлы реализации - это отдельные модули, которые разрабатываются и транслируются независимо друг от друга и объединяются при создании выполняемой программы.
Файлы реализации могут подключать описания, содержащиеся в заголовочных файлах. Сами заголовочные файлы также могут использовать другие заголовочные файлы. Заголовочный файл подключается с помощью директивы препроцессора #include. Например, описания стандартных функций ввода-вывода включаются с помощью строки:
#include <stdio.h>
Имя h-файла записывается в угловых скобках, если этот h-файл является частью стандартной Си-библиотеки и расположен в одном из системных каталогов. Имена h-файлов, созданных самим программистом в рамках разрабатываемого проекта и расположенных в текущем каталоге, указываются в двойных кавычках, например:
#include "mylib.h"
Препроцессор - это программа предварительной обработки текста непосредственно перед трансляцией. Команды препроцессора называются директивами. Директивы препроцессора содержат символ # в начале строки. Препроцессор используется в основном для подключения h-файлов. В Си также очень часто используется директива #define для задания символических имен констант. Пример:
#define PI 3.14159265
Функции[]
Функция является основной структурной единицей языка Си. Функция - это фрагмент программы, который может вызываться из других программ. Функция обычно выполняет алгоритм, который описывается и реализуется отдельно от других алгоритмов. При вызове функции передаются аргументы, которые могут быть использованы в теле функции.
Вызов функции происходит в результате использования ее имени в выражении. За именем функции следуют круглые скобки, внутри которых перечисляются фактические значения ее аргументов. Даже если аргументов нет, круглые скобки с пустым списком аргументов обязательно должны присутствовать!
После вызова функции значение, возвращенное в результате ее выполнения, используется в выражении (имя функции как бы заменяется возвращенным значением). Примеры:
x = sin(1.0); f();
Типы переменных[]
Базовые типы[]
В языке Си используются всего два базовых типа: целые и вещественные числа. Кроме того, имеется фиктивный тип void ("пустота"), который применяется либо для функции, не возвращающей никакого значения, либо для описания указателя общего типа (когда неизвестна информация о типе объекта, на который ссылается указатель).
Целочисленные типы[]
Целочисленные типы различаются по длине в байтах и по наличию знака. Их четыре: char, short, int и long. Кроме того, к описанию можно добавлять модификаторы unsigned или signed для беззнаковых (неотрицательных) или знаковых целых чисел.
Тип int[]
Самый естественный целочисленный тип - это тип int, от слова integer - целое число. Тип int всегда соответствует размеру машинного слова или адреса. Все действия с элементами типа int производятся максимально быстро. Всегда следует выбирать именно тип int, если использование других целочисленных типов не диктуется явно спецификой решаемой задачи. Параметры большинства стандартных функций, работающих с целыми числами или символами, имеют тип int.
В современных архитектурах элемент типа int занимает 4 байта. Элементы типа int трактуются в Си как числа со знаком.
Тип char[]
Тип char представляет целые числа в диапазоне от -128 до 127. Элементы типа char занимают один байт памяти. Слово "char" является сокращением от character, что в переводе означает "символ". Действительно, традиционно символы представляются их целочисленными кодами, а код символа занимает один байт. Тем не менее, подчеркнем, что элементы типа char - это именно целые числа, с ними можно выполнять все арифметические операции. Стандарт Си не устанавливает, трактуются ли элементы типа char как знаковые или беззнаковые числа, но большинство Си-компиляторов считают char знаковым типом.
Вещественные типы[]
Вещественных типов два: длинное вещественное число double (переводится как "двойная точность") и короткое вещественное число float (переводится как "плавающее"). Вещественное число типа double занимает 8 байтов, типа float - 4 байта.
Тип double является основным для компьютера. Тип float - это, скорее, атавизм, оставшийся от ранних версий языка Си. Компьютер умеет производить арифметические действия только с элементами типа double, элементы типа float приходится сначала преобразовывать к double. Точность, которую обеспечивает тип float, низка и не достаточна для большинства практических задач. Все стандартные функции математической библиотеки работают только с типом double.
1. Перечислимый тип[]
Перечислимый тип задаёт тип, который является подмножеством целого типа.
Объявление переменной перечислимого типа задаёт имя переменной и определяет список именованных констант, называемый списком перечисления: enum [<тег>] {<список перечисления>} <описатель> [, <описатель> ...]; enum <тег> <описатель> [, <описатель> ...];
Тег предназначен для различения нескольких перечислимых типов, объявленных в одной программе.
Список перечисления содержит одну или более конструкций вида: <идентификатор> [= <константное выражение>]
Конструкции в списке разделяются запятыми. Каждый идентификатор именует элемент списка перечисления. По умолчанию, если не заданоконстантное выражение, первому элементу присваивается значение 0, следующему элементу – значение 1 и т.д.
Запись = <константное выражение> изменяет умалчиваемую последовательность значений. Элемент, идентификатор которого предшествует записи = <константное выражение>, принимает значение, задаваемое этим константным выражением. Константное выражение должно иметь тип intи может быть как положительным, так и отрицательным. Следующий элемент списка получает значение, равное <константное выражение> + 1, если только его значение не задаётся явно другим константным выражением.
В списке перечисления могут содержаться элементы, которым сопоставлены одинаковые значения, однако каждый идентификатор в списке должен быть уникальным. Кроме того, идентификатор элемента списка перечисления должен быть отличным от идентификаторов элементов всех остальных списков перечислений, а также от других идентификаторов.
enum Weekdays {SA, SU, MO, TU, WE, TH, FR}; | |
enum Weekdays {SA, SU = 0, MO, TU, WE, TH, FR}; | // SA и SU имеют одинаковое значение |
void main() | |
{ enum Weekdays d1 = SA, d2 = SU, d3 = WE, d4; | |
d4 = 2; | // Ошибка! |
d4 = d1 + d2; | // Ошибка! |
d4 = (enum Weekdays)(d1 + d2); | // Можно, но результат |
d4 = (enum Weekdays)(d1 - d2); | // может не попасть |
d4 = (enum Weekdays)(TH * FR); | // в область определения |
d4 = (enum Weekdays)(WE / TU); | // перечисления |
} |
2. Структуры[]
Структура позволяет объединить в одном объекте совокупность значений, которые могут иметь различные типы. Однако в языке С реализован очень ограниченный набор операций над структурами как единым целым: передача функции в качестве аргумента, возврат в качестве значения функции, получение адреса. Можно присваивать одну структуру другой, если они имеют одинаковый тип.
Объявление структуры задает имя структурного типа и/или последовательность объявлений переменных, называемых элементами структуры. Эти элементы могут иметь различные типы. struct [<тег>] {<список объявлений элементов>} <описатель> [, <описатель> ...]; struct <тег> <описатель> [, <описатель> ...];
Тег предназначен для различения нескольких структур, объявленных в одной программе.
Список объявлений элементов представляет собой последовательность из одного или более объявлений переменных. Каждая переменная, объявленная в этом списке, называется элементом структуры. Особенность синтаксиса объявлений элементов структуры состоит в том, что они не могут содержать спецификаций класса памяти и инициализаторов. Элементы структуры могут иметь базовый тип, либо быть массивом, указателем,объединением или структурой.
struct | |
{ char str[50]; | |
int a, b; | // Объявляем структуру, не задавая тег |
} s; | // и сразу же объявляем переменную |
struct S | |
{ char str[50]; | |
int a, b; | |
}; | // Объявляем структуру с тегом S |
struct S s; | // Объявляем переменную |
Элемент структуры не может быть структурой того же типа, в которой он содержится. Однако он может быть указателем на тип структуры, в которую он входит. Размер указателя стандартный, поэтому компилятор знает, сколько памяти потребуется под указатель. Для работы с указателем надо знать размер типа, на который он указывает, но к моменту работы с указателем структура будет полностью объявлена, и, следовательно, размер её будет известен.
Идентификаторы элементов структуры должны различаться между собой. Идентификаторы элементов разных структур могут совпадать.
Для инициализации структуры, как и других составных типов, надо записать список инициализаторов через запятую в фигурных скобках.
struct S s = {"Str", 0, 1}; | // Используем тег S, объявленный в предыдущем примере |
Выбор элемента структуры осуществляется с помощью одной из следующих конструкций: <переменная> . <идентификатор элемента структуры> <указатель> -> <идентификатор элемента структуры>
Выражение выбора элемента позволяет получить доступ к элементу структуры. Выражение имеет значение и тип выбранного элемента.
struct S s, *p = &s; | // Объявляем переменную s и указатель p, в который заносим адрес переменной s |
s.a = 10; | |
p->b = 20; |
Две структуры являются разными типами, даже если их объявления полностью совпадают.
2.1. Пример[]
Вводим массив структур и осуществляем поиск по любой совокупности параметров.
|
|||
struct S | // Объявляем структуру, состоящую | ||
{ char str[21]; int a; }; | // из строки и целого числа | ||
struct S s[10]; | // Объявляем массив структур | ||
void main(int argc, char *argv[]) { int n, a, i, check = 0; | // Переменная а содержит число, которое будет сравниваться с полем структуры а. // Переменная check указывает, нужно ли использовать этот параметр для поиска. | ||
char str[21] = ""; | // Переменная str содержит строку, которая будет сравниваться с полем структуры str. // Если переменная str содержит пустую строку, этот параметр не используется для поиска. | ||
FILE *in, *out; char ans; if (argc < 3) { printf("Too few arguments.\n"); return; } if ((in = fopen(argv[1], "r")) == NULL) { printf("It is impossible to open file '%s'.\n", argv[1]); return; } if ((out = fopen(argv[2], "w")) == NULL) { printf("It is impossible to open file '%s'.\n", argv[2]); fclose(in); return; } for (n = 0; !feof(in); n++) fscanf(in, "%s%d", s[n].str, &s[n].a); fclose(in); printf("Use Str for search? "); ans = getche(); if (ans == 'y' | ans == 'Y') { printf("\nInput string for search: "); scanf("%s",str); } printf("Use A for search? "); ans = getche(); if (ans == 'y' | ans == 'Y') { check = 1; printf("\nInput A: "); scanf("%d", &a); } for (i = 0; i < n; i++) | |
if ((!*str | strcmp(str, s[i].str) == 0) && (!check | a == s[i].a)) fprintf(out, "%-30s %3d\n", s[i].str, s[i].a); fclose(out); } | // Данное условие проверяет содержимое структуры на равенство параметрам поиска, // учитывая необходимость сравнения с этим параметром |
3. Объединения[]
Объединение позволяет в разные моменты времени хранить в одном объекте значения разных типов. В процессе объявления объединения с ним ассоциируется набор типов значений, которые могут храниться в данном объединении. В каждый момент времени объединение может хранить значение только одного типа из набора. Контроль над тем, какого типа значение хранится в данный момент в объединении, возлагается на программиста. union [<тег>] {<список объявлений элементов>} <описатель> [, <описатель> ...]; union <тег> <описатель> [, <описатель> ...];
Тег предназначен для различения нескольких объединений, объявленных в одной программе.
Память, которая выделяется переменной типа объединение, определяется размером наиболее длинного элемента объединения. Все элементы объединения размещаются в одной и той же области памяти с одного и того же адреса. Значение текущего элемента объединения теряется, когда другому элементу объединения присваивается значение.
|
Оператор typedef[]
В языке Си можно задать имя типа, если его описание достаточно громоздко и его не хочется повторять много раз. В дальнейшем можно использовать имя типа при описании переменных. Для определения типа применяется оператор typedef. Синтаксически оператор typedef аналогичен обычному описанию переменной, к которому в самом начале добавлено слово typedef. При этом вместо переменной определяется имя нового типа. Пример:
typedef char *string;
Операторы языка Си[]
Выражения в Си составляются из переменных или констант, к которым применяются различные операции. Для указания порядка операций можно использовать круглые скобки.
Оператор присваивания[]
x = (y = sin(z)) + 1.0;
Арифметические операции[]
z = x + y; z = x - y; z = x * y; z = x / y; z = x % y; // Остаток от деления x на y
Операции увеличения/уменьшения[]
Операции увеличения ++ и уменьшения -- на единицу имеют префиксную и суффиксную формы.
++ x; // Префиксная форма x --; // Суффиксная форма
Разница между префиксной и суффиксной формами проявляется только при вычислении сложных выражений. Если используется префиксная форма операции ++, то сначала переменная увеличивается, и только после этого ее новое значение используется в выражении. При использовании суффиксной формы значение переменной сначала используется в выражении и только затем увеличивается.
Операции "увеличить на", "домножить на" и т.п.[]
Оператор вида ?= существует для любой операции ?, допустимой в Си. Примеры:
z += y; // z = z + y z *= y; // z = z * y z %= x; // z = z % x a ||= b; // a = a || b
Логические операции[]
a = b || c; // логическое "или" a = b && c; // логическое "и" a = !b; // логическое "не"
Самый высокий приоритет у операции логического отрицания, затем следует логическое умножение, самый низкий приоритет у логического сложения.
Чрезвычайно важной особенностью операций логического сложения и умножения является так называемое "сокращенное вычисление" результата. А именно, при вычислении результата операции логического сложения или умножения всегда сначала вычисляется значение первого аргумента. Если оно истинно в случае логического сложения или ложно в случае логического умножения, то второй аргумент операции не вычисляется вовсе! Результат операции полагается истинным в случае логического сложения или ложным в случае логического умножения.
Операции сравнения[]
Операции сравнения в Си обозначаются следующим образом:
== равно, != не равно, > больше, >= больше или равно, < меньше, <= меньше или равно.
Побитовые логические операции[]
Кроме обычных логических операций, в Си имеются побитовые логические операции, которые выполняются независимо для каждого отдельного бита операндов. Побитовые операции имеют следующие обозначения:
& побитовое логическое умножение ("и") | побитовое логическое сложение ("или") ~ побитовое логическое отрицание ("не") ^ побитовое сложение по модулю 2 (исключающее "или")
Операции сдвига[]
Операции сдвига применяются к целочисленным переменным: двоичный код числа сдвигается вправо или влево на указанное количество позиций. Сдвиг вправо обозначается двумя символами "больше" >>, сдвиг влево - двумя символами "меньше" <<. Пример:
int x, y; //... x = (y >> 3); // Сдвиг на 3 позиции вправо y = (y << 2); // Сдвиг на 2 позиции влево
Арифметика указателей[]
С указателями можно выполнять следующие операции:
- сложение/вычитание указателя и целого числа, результат - указатель (указатель как бы сдвигается на n элементов вправо/влево)
- увеличение/уменьшение переменной типа указатель, что эквивалентно прибавлению или вычитанию единицы (указатель как бы сдвигается на 1 элемент вправо/влево)
- вычитание двух указателей, результат - целое число (количество элементов данного типа, которое умещается между двумя адресами)
Приведение типа[]
Операция приведения типа используется, когда значение одного типа преобразуется к другому типу, в том случае, если существует некоторый разумный способ такого преобразования. Операция обозначается именем типа, заключенным в круглые скобки; она записывается перед ее единственным аргументом. Пример:
double x; int n; //... x = (double) n;