1.Описание языка СИ.
1.2.Типы данных и их объявление.
Как уже говорилось выше, все переменные используемые в программах на языке СИ, должны быть объявлены. Тип объявляемой переменной зависит от того, какое ключевое слово используется в качестве спецификатора типа и является ли описатель простым идентификатором или же комбинацией идентификатора с модификатором указателя (звездочка), массива (квадратные скобки) или функции (круглые скобки).
При объявлении простой переменной, структуры, смеси или объединения, а также перечисления, описатель - это простой идентификатор. Для объявления указателя, массива или функции идентификатор модифицируется соответствующим образом: звездочкой слева, квадратными или круглыми скобками справа.
Отметим важную особенность языка СИ, при объявлении можно использовать одновременно более одного модификатора, что дает возможность создавать множество различных сложных описателей типов.
Однако надо помнить, что некоторые комбинации модификаторов недопустимы:
- элементами массивов не могут быть функции,
- функции не могут возвращать массивы или функции.
При инициализации сложных описателей квадратные и круглые скобки (справа от идентификатора) имеют приоритет перед звездочкой (слева от идентификатора). Квадратные или круглые скобки имеют один и тот же приоритет и раскрываются слева направо. Спецификатор типа рассматривается на последнем шаге, когда описатель уже полностью проинтерпретирован. Можно использовать круглые скобки, чтобы поменять порядок интерпретации на необходимый.
Для интерпретации сложных описаний предлагается простое правило, которое звучит как "изнутри наружу", и состоит из четырех шагов.
1. Начать с идентификатора и посмотреть вправо, есть ли квадратные или круглые скобки.
2. Если они есть, то проинтерпретировать эту часть описателя и затем посмотреть налево в поиске звездочки.
3. Если на любой стадии справа встретится закрывающая круглая скобка, то вначале необходимо применить все эти правила внутри круглых скобок, а затем продолжить интерпретацию.
4. Интерпретировать спецификатор типа.
Примеры:
int * ( * comp [10]) ();
6 5 3 1 2 4
В данном примере объявляется переменная comp (1), как массив из десяти (2) указателей (3) на функции (4), возвращающие указатели (5) на целые значения (6).
char * ( * ( * var ) () ) [10];
7 6 4 2 1 3 5
Переменная var (1) объявлена как указатель (2) на функцию (3) возвращающую указатель (4) на массив (5) из 10 элементов, которые являются указателями (6) на значения типа char.
Кроме объявлений переменных различных типов, имеется возможность объявить типы. Это можно сделать двумя способами. Первый способ - указать имя тега при объявлении структуры, объединения или перечисления, а затем использовать это имя в объявлении переменных и функций в качестве ссылки на этот тег. Второй - использовать для объявления типа ключевое слово typedef.
При объявлении с ключевым словом typedef, идентификатор стоящий на месте описываемого объекта, является именем вводимого в рассмотрение типа данных, и далее этот тип может быть использован для объявления переменных.
Отметим, что любой тип может быть объявлен с использованием ключевого слова typedef, включая типы указателя, функции или массива. Имя с ключевым словом typedef для типов указателя, структуры, объединения может быть объявлено прежде чем эти типы будут определенны, но в пределах видимости объявителя.
Примеры:
typedef double (* MATH)( );
/* MATH - новое имя типа, представляющее указатель на
функцию, возвращающую значения типа double */
MATH cos;
/* cos указатель на функцию, возвращающую
значения типа double */
/* Можно провести эквивалентное объявление */
double (* cos)( );
typedef char FIO[40]
/* FIO - массив из сорока символов */
FIO person;
/* Переменная person - массив из сорока символов */
/* Это эквивалентно объявлению */
char person[40];
При объявлении переменных и типов здесь были использованы имена типов (MATH FIO). Помимо этого, имена типов могут еще использоваться в трех случаях: в списке формальных параметров, в объявлении функций, в операциях приведения типов и в операции sizeof (операция приведения типа).
Именами типов для основных типов, типов перечисления, структуры и смеси являются спецификаторы типов для этих типов. Имена типов для типов указателя массива и функции задаются при помощи абстрактных описателей следующим образом:
спецификатор-типа абстрактный-описатель;
Абстрактный-описатель - это описатель без идентификатора, состоящий из одного или более модификаторов указателя, массива или функции. Модификатор указателя (*) всегда задается перед идентификатором в описателе, а модификаторы массива [] и функции () - после него. Таким образом, чтобы правильно интерпретировать абстрактный описатель, нужно начать интерпретацию с подразумеваемого идентификатора.
Абстрактные описатели могут быть сложными. Скобки в сложных абстрактных описателе задают порядок интерпретации подобно тому, как это делалось при интерпретации сложных описателей в объявлениях.
При объявлении переменной ей можно присвоить начальное значение, присоединяя инициатор к описателю. Инициатор начинается со знака "=" и имеет следующие формы.
Формат 1: = инициатор;
Формат 2: = { список - инициаторов };
Формат 1 используется при инициализации переменных основных типов и указателей, а формат 2 - при инициализации составных объектов.
Примеры:
char tol = 'N';
Переменная tol инициализируется символом 'N'.
const long megabute = (1024 * 1024);
Немодифицируемая переменная megabute инициализируется константным выражением после чего она не может быть изменена.
static int b[2][2] = {1,2,3,4};
Инициализируется двухмерный массив b целых величин элементам массива присваиваются значения из списка. Эта же инициализация может быть выполнена следующим образом :
static int b[2][2] = { { 1,2 }, { 3,4 } };
При инициализации массива можно опустить одну или несколько размерностей
static int b[3[] = { { 1,2 }, { 3,4 } };
Если при инициализации указано меньше значений для строк, то оставшиеся элементы инициализируются 0, т.е. при описании
static int b[2][2] = { { 1,2 }, { 3 } };
элементы первой строки получат значения 1 и 2, а второй 3 и 0.
При инициализации составных объектов, нужно внимательно следить за использованием скобок и списков инициализаторов.
Примеры:
struct complex { double real;
double imag; } comp [2][3] =
{ { {1,1}, {2,3}, {4,5} },
{ {6,7}, {8,9}, {10,11} } };
В данном примере инициализируется массив структур comp из двух строк и трех столбцов, где каждая структура состоит из двух элементов real и imag.
struct complex comp2 [2][3] =
{ {1,1},{2,3},{4,5}, {6,7},{8,9},{10,11} };
В этом примере компилятор интерпретирует рассматриваемые фигурные скобки следующим образом:
- первая левая фигурная скобка - начало составного инициатора для массива comp2;
- вторая левая фигурная скобка - начало инициализации первой строки массива comp2[0]. Значения 1,1 присваиваются двум элементам первой структуры;
- первая правая скобка (после 1) указывает компилятору, что список инициаторов для строки массива окончен, и элементы оставшихся структур в строке comp[0] автоматически инициализируются нулем;
- аналогично список {2,3} инициализирует первую структуру в строке comp[1], а оставшиеся структуры массива обращаются в нули;
- на следующий список инициализаторов {4,5} компилятор будет сообщать о возможной ошибке так как строка 3 в массиве comp2 отсутствует.
При инициализации объединения задается значение первого элемента объединения в соответствии с его типом.
Пример:
union tab { unsigned char name[10];
int tab1;
} pers = {'A','H','T','O','H'};
Инициализируется переменная pers.name, и так как это массив, для его инициализации требуется список значений в фигурных скобках. Первые пять элементов массива инициализируются значениями из списка, остальные нулями.
Инициализацию массива символов можно выполнить путем использования строкового литерала.
char stroka[ ] = "привет";
Инициализируется массив символов из 7 элементов, последним элементом (седьмым) будет символ '\0', которым завершаются все строковые литералы.
В том случае, если задается размер массива, а строковый литерал длиннее, чем размер массива, то лишние символы отбрасываются.
Следующее объявление инициализирует переменную stroka как массив, состоящий из семи элементов.
char stroka[5] = "привет";
В переменную stroka попадают первые пять элементов литерала, а символы 'Т' и '\0' отбрасываются.
Если строка короче, чем размер массива, то оставшиеся элементы массива заполняются нулями.
Отметим, что инициализация переменной типа tab может иметь следующий вид:
union tab pers1 = "Антон";
и, таким образом, в символьный массив попадут символы:
'А','Н','Т','О','Н','\0',
а остальные элементы будут инициализированы нулем.