Это госы! Wiki
Это госы! Wiki
Advertisement

Структура Си-программы[]

Любая достаточно большая программа на Си состоит из файлов. Файлы транслируются Си-компилятором независимо друг от друга и затем объединяются программой-построителем задач, в результате чего создается файл с программой, готовой к выполнению. Файлы, содержащие тексты Си-программы, называются исходными.
В языке Си исходные файлы бывают двух типов:

  • заголовочные, или 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. Пример[]

Вводим массив структур и осуществляем поиск по любой совокупности параметров.

  1. include <stdio.h>
  2. include <math.h>
  3. include <string.h>
  4. include <conio.h>
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 <тег> <описатель> [, <описатель> ...];

Тег предназначен для различения нескольких объединений, объявленных в одной программе.

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

  1. include <stdio.h> void main() { union { float f; long int i; } u; printf("Input float number: "); scanf("%f", &u.f); printf("Internal: %08x\n\n", u.i); }

Оператор 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;
Advertisement