Синтаксис русского языка
- 1 year ago
- 0
- 0
Синтаксис определяет то, как должны правильно записываться языковые конструкции, в то время как семантика определяет значения языковых конструкций . Синтаксис языка Си достаточно сложный, а семантика неоднозначная . Основными двумя особенностями языка на момент его появления были унифицирование работы с массивами и указателями, а также схожесть того, как что-либо объявляется, с тем, как это в дальнейшем используется в выражениях . Однако в последующем эти две особенности языка были в числе наиболее критикуемых , и обе являются сложными для понимания среди начинающих программистов . Стандарт языка, определяя его семантику, не стал слишком сильно ограничивать реализации языка компиляторами, но этим самым сделал семантику недостаточно определённой. В частности, в стандарте есть 3 типа недостаточно определённой семантики: определяемое реализацией поведение, не заданное стандартом поведение и неопределённое поведение .
В языке используются все символы латинского алфавита , цифры и некоторые специальные символы .
Символы латинского алфавита |
|
Цифры |
0
,
1
,
2
,
3
,
4
,
5
,
6
,
7
,
8
,
9
|
Специальные символы |
,
(запятая)
,
;
,
.
(точка)
,
+
,
-
,
*
,
^
,
&
(амперсанд)
,
=
,
~
(тильда)
,
!
,
/
,
<
,
>
,
(
,
)
,
{
,
}
,
[
,
]
,
|
,
%
,
?
,
'
(апостроф)
,
"
(кавычки)
,
:
(двоеточие)
,
_
(знак подчёркивания)
,
\
,
#
|
Из допустимых символов формируются лексемы — предопределённые константы , идентификаторы и знаки операций . В свою очередь, лексемы являются частью выражений ; а из выражений составляются инструкции и операторы .
При трансляции программы на Си из программного кода выделяются лексемы максимальной длины, содержащие допустимые символы. Если в программе имеется недопустимый символ, то лексический анализатор (или компилятор) выдаст ошибку, и трансляция программы окажется невозможной.
Символ
#
не может быть частью никакой лексемы и используется в препроцессоре
.
Допустимый идентификатор — это слово, в состав которого могут входить символы латинского алфавита, цифры и знак подчёркивания . Идентификаторы даются операторам, константам, переменным, типам и функциям.
В качестве идентификаторов программных объектов не могут использоваться идентификаторы ключевых слов и встроенные идентификаторы. Существуют и зарезервированные идентификаторы, на использование которых компилятор не выдаст ошибок, но которые в будущем могут стать ключевыми словами, что повлечёт за собой несовместимость.
Встроенный идентификатор только один —
__func__
, который определяется как константная строка, неявно объявляемая в каждой функции и содержащая её название
.
Специально оформленные литералы в Си принято называть константами. Литеральные константы могут быть целочисленными, вещественными, символьными и строковыми .
Целые числа по умолчанию задаются в
десятичной системе счисления
. Если указан префикс
0x
, то — в
шестнадцатеричной системе
. Префикс в виде цифры 0 указывает, что число задаётся в
восьмеричной системе
. Суффикс определяет минимальный размер типа константы, а также определяет, является ли число знаковым или беззнаковым. В качестве итогового типа берётся такой минимально возможный, в котором данную константу можно представить
.
Суффикс | Для десятичных | Для восьмеричных и шестнадцатеричных |
---|---|---|
Нет |
int
|
int
|
u
или
U
|
unsigned int
|
unsigned int
|
l
или
L
|
long
|
long
|
u
или
U
вместе с
l
или
L
|
unsigned long
|
unsigned long
|
ll
или
LL
|
long long
|
long long
|
u
или
U
вместе с
ll
или
LL
|
unsigned long long
|
unsigned long long
|
Десятичный
формат |
С экспонентой |
Шестнадцатеричный
формат |
---|---|---|
1.5
|
1.5e+0
|
0x1.8p+0
|
15e-1
|
0x3.0p-1
|
|
0.15e+1
|
0x0.cp+1
|
Константы вещественных чисел по умолчанию имеют тип
double
. При указании суффикса
f
константе назначается тип
float
, а при указании
l
или
L
—
long double
. Константа будет считаться вещественной, если в ней присутствует знак точки, либо буквы
p
или
P
в случае шестнадцатеричной записи с префиксом
0x
. Десятичная запись может включать экспоненту, указываемую после букв
e
или
E
. В случае шестнадцатеричной записи экспонента указывается после букв
p
или
P
в обязательном порядке, что отличает вещественные шестнадцатеричные константы от целых. В шестнадцатеричном виде экспонента является степенью числа 2
.
Символьные константы заключаются в одинарные кавычки (
'
), а префикс задаёт как тип данных символьной константы, так и кодировку, в которой символ будет представлен. В Си символьная константа без префикса имеет тип
int
, в отличие от
C++
, в котором символьной константе соответствует
char
.
Префикс | Тип данных | Кодировка |
---|---|---|
Нет |
int
|
ASCII |
u
|
char16_t
|
Кодировка 16-битных многобайтовых строк |
U
|
char32_t
|
Кодировка 32-битных многобайтовых строк |
L
|
wchar_t
|
Кодировка широких строк |
Строковые литералы заключаются в двойные кавычки и могут иметь префикс, определяющий тип данных строки и её кодировку. Строковые литералы представляют собой обычные массивы. При этом в многобайтовых кодировках, таких как
UTF-8
, один символ может занимать более одного элемента массива. По факту строковые литералы являются константными
, но в отличие от C++ их типы данных не содержат модификатор
const
.
Префикс | Тип данных | Кодировка |
---|---|---|
Нет |
char *
|
ASCII или многобайтовая кодировка |
u8
|
char *
|
UTF-8 |
u
|
char16_t *
|
16-битная многобайтовая кодировка |
U
|
char32_t *
|
32-битная многобайтовая кодировка |
L
|
wchar_t *
|
Кодировка широких строк |
Несколько подряд идущих строковых констант, разделённых пробельными символами или переводами строк объединяются в одну строку при компиляции, что часто используется для оформления кода строки путём разделения частей строковой константы по разным строкам для повышения читабельности .
Макрос |
#define BUFFER_SIZE 1024
|
Анонимное
перечисление |
enum {
BUFFER_SIZE = 1024
};
|
Переменная
в роли константы |
const int
buffer_size = 1024;
extern const int
buffer_size;
|
В языке Си для задания констант принято использовать макроопределения, объявляемые с помощью директивы препроцессора
#define
:
#define
имя константы
[
значение
]
Введённая таким образом константа будет действовать в области своей видимости, начиная с момента задания константы и до конца программного кода или до тех пор, пока действие заданной константы не будет отменено директивой
#undef
:
#undef
имя константы
Как и для всякого макроса, для именованной константы происходит автоматическая подстановка значения константы в программном коде всюду, где употреблено имя константы. Поэтому при объявлении внутри макроса целых или вещественных чисел может понадобиться явно указывать тип данных с помощью соответствующего суффикса литерала, иначе число по умолчанию будет иметь тип
int
в случае целого или тип
double
— в случае вещественного.
Для целых чисел существует другой способ создания именованных констант — через перечисления оператора
enum
. Однако данный метод подходит только для типов, размером меньших либо равных типу
int
, и не используется в стандартной библиотеке
.
Также можно создавать константы в виде переменных с квалификатором
const
, но в отличие от двух других способов, такие константы потребляют память, на них можно получить указатель, и их нельзя использовать на этапе компиляции
:
case
.
Ключевые слова — это идентификаторы, предназначенные для выполнения той или иной задачи на этапе компиляции, либо для подсказок и указаний компилятору.
Ключевые слова | Назначение | Стандарт |
---|---|---|
sizeof
|
Получение размера объекта на этапе компиляции | C89 |
typedef
|
Задание альтернативного имени типу | |
auto
,
register
|
Подсказки компилятору по месту хранения переменных | |
extern
|
Указание компилятору искать объект вне текущего файла | |
static
|
Объявление статического объекта | |
void
|
Маркер отсутствия значения; в указателях означает произвольные данные | |
char
,
short
,
int
,
long
|
Целочисленные типы и модификаторы их размера | |
signed
,
unsigned
|
Модификаторы целочисленных типов, определяющие их как знаковые или беззнаковые | |
float
,
double
|
Вещественные типы данных | |
const
|
Модификатор типа данных, указывающий компилятору, что переменные этого типа доступны только для чтения | |
volatile
|
Указание компилятору на возможность изменения значения переменной извне | |
struct
|
Тип данных в виде структуры с набором полей | |
enum
|
Тип данных, хранящий одно из набора целочисленных значений | |
union
|
Тип данных, в котором можно хранить данные в представлениях разных типов данных | |
do
,
for
,
while
|
Операторы цикла | |
if
,
else
|
Условный оператор | |
switch
,
case
,
default
|
Оператор выбора по целочисленному параметру | |
break
,
continue
|
Операторы прерывания цикла | |
goto
|
Оператор безусловного перехода | |
return
|
Возврат из функции | |
inline
|
Объявление встраиваемой функции | C99 |
restrict
|
Объявление указателя, который ссылается на блок памяти, на который не ссылается никакой другой указатель | |
_Bool
|
Булев тип данных | |
_Complex
,
_Imaginary
|
Типы, используемые для вычислений с комплексными числами | |
_Atomic
|
Модификатор типа, делающий его атомарным | C11 |
_Alignas
|
Явное задание выравнивания в байтах для типа данных | |
_Alignof
|
Получение выравнивания для заданного типа данных на этапе компиляции | |
_Generic
|
Выбор одного из набора значений на этапе компиляции, исходя из контролируемого типа данных | |
_Noreturn
|
Указание компилятору, что функция не может завершаться нормальным образом (то есть по
return
)
|
|
_Static_assert
|
Указание утверждений, проверяемых на этапе компиляции | |
_Thread_local
|
Объявление локальной для потока переменной |
Помимо ключевых слов стандарт языка определяет зарезервированные идентификаторы, использование которых может привести к несовместимости с будущими версиями стандарта. Зарезервированными являются все, за исключением ключевых, слова, начинающиеся со знака подчёркивания (
_
), после которого идёт либо заглавная буква (
A
—
Z
), либо другой знак подчёркивания
. В стандартах С99 и С11 часть таких идентификаторов была использована под новые ключевые слова языка.
В области видимости файла зарезервировано использование любых имён, начинающихся со знака подчёркивания (
_
)
, то есть со знака подчёркивания допускается именовать типы, константы и переменные, объявленные в рамках какого-либо блока инструкций, например, внутри функций.
Также зарезервированными идентификаторами являются все макросы стандартной библиотеки и связываемые на этапе линковки названия из неё .
Использование зарезервированных идентификаторов в программах стандарт определяет как
неопределённое поведение
. Попытка отмены любого стандартного макроса через
#undef
также повлечёт за собой неопределённое поведение
.
Текст программы на Си может содержать фрагменты, которые не являются частью программного кода, — комментарии . Комментарии специальным образом помечаются в тексте программы и пропускаются при компиляции.
Первоначально, в стандарте
C89
, были доступны встраиваемые комментарии, которые могли помещаться между последовательностями символов
/*
и
*/
. При этом невозможно вложить один комментарий в другой, поскольку первая встреченная последовательность
*/
завершит комментарий, а текст, следующий непосредственно за обозначением
*/
, будет воспринят компилятором как исходный текст программы.
Следующий стандарт,
C99
, ввёл ещё один способ оформления комментариев: комментарием считается текст, начинающийся с последовательности символов
//
и заканчивающийся концом строки
.
Комментарии часто используются для самодокументирования исходного кода, поясняя работу сложных частей, описывая назначение тех или иных файлов, а также описывая правила использования и работу тех или иных функций, макросов, типов данных и переменных. Существуют постпроцессоры, которые умеют преобразовывать специально оформленные комментарии в документацию. Среди таких постпроцессоров с языком Си умеет работать система документирования Doxygen .
Операторы, применяемые в выражениях, представляют собой некоторую операцию, которая выполняется над операндами и которая возвращает вычисленное значение — результат выполнения операции. В качестве операнда может выступать константа, переменная, выражение или вызов функции. Оператор может представлять собой специальный символ, набор специальных символов или служебное слово. Операторы различают по количеству задействованных операндов, а именно — различают унарные операторы, бинарные операторы и тернарные операторы.
Унарные операторы выполняют операцию над единственным аргументом и имеют следующий формат операции:
Операции постфиксного инкремента и декремента имеют обратный формат:
+
|
Унарный плюс |
~
|
Взятие обратного кода |
&
|
Взятие адреса |
++
|
Префиксный или постфиксный инкремент |
sizeof
|
Получение количества байт, занимаемого объектом в памяти; может использоваться и как операция, и как оператор |
-
|
Унарный минус |
!
|
логическое отрицание |
*
|
Разыменовывание указателя |
--
|
Префиксный или постфиксный декремент |
_Alignof
|
Получение выравнивания для заданного типа данных |
Операторы инкремента и декремента, в отличие от остальных унарных операторов, изменяют значение своего операнда. Префиксный оператор сначала изменяет значение, а затем возвращает его. Постфиксный же сначала возвращает значение, а только потом его изменяет.
Бинарные операторы располагаются между двумя аргументами и осуществляют операцию над ними:
+
|
Сложение |
%
|
Взятие остатка от деления |
<<
|
Поразрядный сдвиг влево |
>
|
Больше |
==
|
Равно |
-
|
Вычитание |
&
|
Поразрядное И |
>>
|
Поразрядный сдвиг вправо |
<
|
Меньше |
!=
|
Не равно |
*
|
Умножение |
|
|
Поразрядное ИЛИ |
&&
|
Логическое И |
>=
|
Больше либо равно | , | Последовательное вычисление |
/
|
Деление |
^
|
Поразрядное исключающее ИЛИ |
||
|
Логическое ИЛИ |
<=
|
Меньше либо равно |
Также к бинарным операторам в Си относятся лево-присваивающие операторы, которые производят операцию над левым и правым аргументом и заносят результат в левый аргумент.
=
|
Присвоение значения правого аргумента левому |
%=
|
Остаток от деления левого операнда на правый |
^=
|
Поразрядное исключающее ИЛИ правого операнда к левому |
+=
|
Прибавление к левому операнду правого |
/=
|
Деление левого операнда на правый |
<<=
|
Поразрядный сдвиг левого операнда влево на количество бит, заданное правым операндом |
-=
|
Вычитание из левого операнда правого |
&=
|
Поразрядное И правого операнда к левому |
>>=
|
Поразрядный сдвиг левого операнда вправо на количество бит, заданное правым операндом |
*=
|
Умножение левого операнда на правый |
|=
|
Порязрядное ИЛИ правого операнда к левому |
В Си имеется единственный тернарный оператор — сокращённый условный оператор, который имеет следующий вид:
?
[
выражение1
]
:
[
выражение2
]
Сокращённый условный оператор имеет три операнда:
Оператором в данном случае является сочетание знаков
?
и
:
.
Выражение — это упорядоченный набор операций над константами, переменными и функциями. Выражения содержат операции, состоящие из операндов и операторов
. Порядок выполнения операций зависит от формы записи и от приоритета выполнения операций. У каждого выражения имеется значение — результат выполнения всех операций, входящих в выражение. В ходе вычисления выражения в зависимости от операций могут изменяться значения переменных, а также могут исполняться функции, если их вызовы присутствуют в выражении.Среди выражений выделяют класс лево-допустимых выражений — выражений, которые могут присутствовать слева от знака присваивания.
Приоритет операций определяется стандартом и задаёт порядок, в котором операции будут производиться. Операции в Си выполняются в соответствии приведённой ниже таблице приоритетов .
Приоритет | Лексемы | Операция | Класс | Ассоциативность |
---|---|---|---|---|
1 |
a[
индекс
]
|
Обращение по индексу | постфиксный | слева направо → |
f(
аргументы
)
|
Вызов функции | |||
.
|
Доступ к полю | |||
->
|
Доступ к полю по указателю | |||
++
--
|
Положительное и отрицательное приращение | |||
(
имя типа
)
{
инициализатор
}
|
Составной литерал (C99) | |||
(
имя типа
)
{
инициализатор,
}
|
||||
2 |
++
--
|
Положительное и отрицательное префиксные приращения | унарный | ← справа налево |
sizeof
|
Получение размера | |||
_Alignof
|
Получение выравнивания ( C11 ) | |||
~
|
Побитовое НЕ | |||
!
|
Логическое НЕ | |||
-
+
|
Указание знака (минус или плюс) | |||
&
|
Получение адреса | |||
*
|
Обращение по указателю (разыменовывание) | |||
(
имя типа
)
|
Приведение типа | |||
3 |
*
/
%
|
Умножение, деление и получение остатка | бинарный | слева направо → |
4 |
+
-
|
Сложение и вычитание | ||
5 |
<<
>>
|
Сдвиг влево и вправо | ||
6 |
<
>
<=
>=
|
Операции сравнения | ||
7 |
==
!=
|
Проверка на равенство или неравенство | ||
8 |
&
|
Побитовое И | ||
9 |
^
|
Побитовое исключающее ИЛИ | ||
10 |
|
|
Побитовое ИЛИ | ||
11 |
&&
|
Логическое И | ||
12 |
||
|
Логическое ИЛИ | ||
13 |
?
:
|
Условие | тернарный | ← справа налево |
14 |
=
|
Присвоение значения | бинарный | |
+=
-=
*=
/=
%=
<<=
>>=
&=
^=
|=
|
Операции изменения левого значения | |||
15 |
,
|
Последовательное вычисление | слева направо → |
Приоритеты операций в Си не всегда себя оправдывают и иногда приводят к интуитивно трудно предсказуемым результатам. Например, поскольку унарные операторы имеют ассоциативность справа налево, то вычисление выражения
*p++
приведёт к увеличению указателя с последующим разыменовыванием (
*(p++)
), а не к увеличению значения по указателю (
(*p)++
). Поэтому в случае сложных для понимания ситуаций рекомендуется явно группировать выражения с помощью скобок
.
Другой важной особенностью языка Си является то, что вычисление значений аргументов, передаваемых в вызов функции не является последовательным , то есть запятая, разделяющая аргументы, не соответствует последовательному вычислению из таблицы приоритетов. В следующем примере вызовы функций, указываемые в качестве аргументов другой функции, могут идти в произвольном порядке:
int x;
x = compute(get_arg1(), get_arg2()); // первым может быть вызов get_arg2()
Также нельзя полагаться на приоритет операций в случае наличия побочных эффектов , появляющихся в ходе вычисления выражения, поскольку это будет приводить к неопределённому поведению .
Приложение C стандарта языка определяет набор точек следования , в которых гарантируется отсутствие текущих побочных эффектов от вычислений. То есть точка следования — это этап вычислений, который разделяет вычисление выражений между собой так, что произошедшие до точки следования вычисления, включая побочные эффекты, уже закончились, а после точки следования — ещё не начинались . Побочным эффектом может быть изменение значения переменной в ходе вычисления выражения. Изменение значения, участвующего в вычислениях, вместе с побочным изменением этого же значения до следующей точки следования будет приводить к неопределённому поведению. То же самое будет, если происходит два или более побочных изменений одного и того же значения, участвующего в вычислениях .
Точка следования | Событие до | Событие после |
---|---|---|
Вызов функции | Вычисление указателя на функцию и её аргументов | Вызов функции |
Операторы логического И (
&&
), ИЛИ (
||
) и последовательное вычисление (
,
)
|
Вычисление первого операнда | Вычисление второго операнда |
Сокращённый оператор условия (
?:
)
|
Вычисление операнда, выступающего условием | Вычисление 2-го или 3-го операндов |
Между двумя полными выражениями (не вложенными) | Одно полное выражение | Следующее полное выражение |
Законченный полный описатель | ||
Сразу перед возвратом из библиотечной функции | ||
После каждого преобразования, связанного со спецификатором форматированного ввода-вывода | ||
Сразу перед и сразу после каждого вызова функции сравнения, а также между вызовом функции сравнения и любыми перемещениями, выполняемыми над передаваемыми в функцию сравнения аргументами |
Полными выражениями считаются :
if
) или оператора выбора (
switch
);
while
с предусловием или с постусловием;
for
, если таковой указан;
return
, если таковое указано.
В следующем примере переменная изменяется трижды между точками следования, что приводит к неопределённому результату:
int i = 1; // Описатель - первая точка следования, полное выражение - вторая
i += ++i + 1; // Полное выражение - третья точка следования
printf("%d\n", i); // Может быть выведено как 4, так и 5
Другие простые примеры неопределённого поведения, которого необходимо избегать:
i = i++ + 1; // неопределённое поведение
i = ++i + 1; // тоже неопределённое поведение
printf("%d, %d\n", --i, ++i); // неопределённое поведение
printf("%d, %d\n", ++i, ++i); // тоже неопределённое поведение
printf("%d, %d\n", i = 0, i = 1); // неопределённое поведение
printf("%d, %d\n", i = 0, i = 0); // тоже неопределённое поведение
a[i] = i++; // неопределённое поведение
a[i++] = i; // тоже неопределённое поведение
Управляющие операторы предназначены для осуществления действий и для управления ходом выполнения программы. Несколько идущих подряд операторов образуют последовательность операторов .
Самая простая языковая конструкция — это пустое выражение, называемое пустым оператором :
;
Пустой оператор не совершает никаких действий и может находиться в любом месте программы. Обычно используется в циклах с отсутствующим телом .
Инструкция — это некое элементарное действие:
;
Действие этого оператора заключается в выполнении указанного в теле оператора выражения.
Несколько идущих подряд инструкций образуют последовательность инструкций .
Инструкции могут быть сгруппированы в специальные блоки следующего вида:
{
}
,
Блок инструкций, также иногда называемый составным оператором, ограничивается левой фигурной скобкой (
{
) в начале и правой фигурной скобкой (
}
) — в конце.
В функциях
блок инструкций обозначает тело функции и является частью определения функции. Также составной оператор может использоваться в операторах циклов, условия и выбора.В языке существует два условных оператора, реализующих ветвление программы:
if
, содержащий проверку одного условия,
switch
, содержащий проверку нескольких условий.
Самая простая форма оператора
if
if
(
(
условие
)
)
(
оператор
)
Оператор
if
работает следующим образом:
if
.
if
.
В частности, следующий ниже код, в случае выполнения заданного условия, не будет выполнять никаких действий, поскольку, фактически, выполняется пустой оператор:
if
(
(
условие
)
)
;
Более сложная форма оператора
if
содержит ключевое слово
else
:
if
(
(
условие
)
)
(
оператор
)
else
(
альтернативный оператор
)
Здесь, если условие, указанное в скобках, не выполнено, то выполняется оператор, указанный после ключевого слова
else
.
Несмотря на то, что стандарт допускает указание тела операторов
if
или
else
одной строкой, это считается плохим стилем, снижающим читабельность кода. В качестве тела рекомендуется всегда указывать блок инструкций с помощью фигурный скобок
.
Цикл — это фрагмент программного кода, содержащий
В соответствии с этим, различают два вида циклов:
Цикл с постусловием гарантирует, что тело цикла выполнится по крайней мере один раз.
В языке Си предусмотрено два варианта циклов с предусловием:
while
и
for
.
while
(
условие
)
[
тело цикла
]
for
(
блок инициализации
;
условие
;
оператор
)
[
тело цикла
],
Цикл
for
ещё называется параметрическим, он эквивалентен следующему блоку операторов:
while
(
условие
)
{
}
В обычной ситуации блок инициализации содержит задание начального значения переменной, которая называется переменной цикла, а оператор, который выполняется сразу после тела цикла, меняет значения используемой переменной, условие содержит сравнение значения используемой переменной цикла с некоторым заранее заданным значением, и, как только сравнение перестаёт выполняться, цикл прерывается, и начинает выполняться программный код, следующий сразу за оператором цикла.
У цикла
do-while
условие указывается после тела цикла:
do
[
тело цикла
]
while
(
условие
)
Условие цикла — это логическое выражение. Однако неявное приведение типов позволяет использовать в качестве условия цикла арифметическое выражение. Это позволяет организовать так называемый «бесконечный цикл»:
while
(1);
То же самое можно сделать и с применением оператора
for
:
for
(;;);
На практике такие бесконечные циклы обычно используются совместно с операторами
break
,
goto
или
return
, которые осуществляют прерывание работы цикла разными способами.
Как и для оператора условия, использование однострочного тела без заключения его в блок инструкций с помощью фигурных скобок считается плохим стилем, снижающим читабельность кода .
Операторы безусловного перехода позволяют прервать выполнение любого блока вычислений и перейти в другое место программы в рамках текущей функции. Операторы безусловного перехода обычно используются совместно с условными операторами.
goto
[
метка
],
Метка — это некоторый идентификатор, передаёт управление тому оператору, который помечен в программе указанной меткой:
:
[
оператор
]
Если указанная метка отсутствует в программе или если существует несколько операторов с одной и той же меткой, компилятор сообщает об ошибке.
Передача управления возможна только в пределах той функции, где используется оператор перехода, следовательно, при помощи оператора
goto
нельзя передать управление в другую функцию.
Другие операторы перехода связаны с циклами и позволяют прервать выполнения тела цикла:
break
немедленно прерывает выполнение тела цикла, и происходит передача управления на оператор, следующий непосредственно сразу за циклом;
continue
прерывает выполнение текущей итерации цикла и инициирует попытку перехода к следующей.
Оператор
break
также может прерывать работу оператора
switch
, поэтому внутри оператора
switch
, запущенного в цикле, оператор
break
не сможет прервать работу цикла. Указанный в теле цикла, он прерывает работу ближайшего вложенного цикла.
Оператор
continue
может быть использован только внутри операторов
do
,
while
и
for
. У циклов
while
и
do-while
оператор
continue
вызывает проверку условия цикла, а в случае цикла
for
— исполнение оператора, заданного в 3-м параметре цикла, перед проверкой условия продолжения цикла.
Оператор
return
прерывает выполнение той функции, в которой использован. Если функция не должна возвращать значение, то используется вызов без возвращаемого значения:
return
;
Если функция должна возвращать какое-либо значение, то после оператора указывается возвращаемое значения:
return
[
значение
]
;
Если после оператора возврата в теле функции имеются ещё какие-то операторы, то эти операторы никогда не будут выполняться, и в этом случае компилятор может выдать предупреждение. Однако после оператора
return
могут указываться инструкции для альтернативного завершения функции, например, по ошибке, а переход к этим операторам можно осуществлять с помощью оператора
goto
согласно каким-либо условиям
.
При объявлении переменной указывается её тип
и название, а также может указываться начальное значение:
;
или
=
[инициализатор]
;
,
где
Если переменной не присвоено начальное значение, то в случае глобальной переменной её значение заполняется нулями, а для локальной переменной начальное значение будет неопределённым.
В описателе переменной можно обозначать переменную как глобальную, но ограниченную областью видимости файла или функции, с помощью ключевого слова
static
. Если переменная объявлена глобальной без ключевого слова
static
, то обращаться к ней возможно и из других файлов, где требуется объявить данную переменную без инициализатора, но с ключевым словом
extern
. Адреса таких переменных определяются на этапе
компоновки
.
Функция — это самостоятельный фрагмент программного кода, который может многократно использоваться в программе. Функции могут иметь аргументы и могут возвращать значения. Также функции могут иметь побочные эффекты при своём исполнении: изменение глобальных переменных, работа с файлами, взаимодействие с операционной системой или оборудованием .
Для того, чтобы задать функцию в Си, необходимо её объявить:
Также необходимо привести определение функции, которое содержит блок операторов, реализующих поведение функции.
Отсутствие объявления определённой функции является ошибкой, если функция используется вне области видимости определения, что, в зависимости от реализации, приводит к выдаче сообщений или предупреждений.
Для вызова функции достаточно указать её имя с параметрами, указанными в скобках. При этом адрес точки вызова помещается в стек, создаются и инициализируются переменные, отвечающие за параметры функции, и передаётся управление коду, реализующему вызываемую функцию. После выполнения функции происходит освобождение памяти, выделенной при вызове функции, возврат в точку вызова и, если вызов функции является частью некоторого выражения, передача в точку возврата вычисленного внутри функции значения.
Если после функции не указаны скобки, то компилятор интерпретирует это как получение адреса функции. Адрес функции можно заносить в указатель и в последующем вызывать функцию посредством указателя на неё, что активно используется, например, в системах плагинов .
С помощью ключевого слова
inline
можно помечать функции, вызовы которых требуется исполнять как можно быстрее. Компилятор может подставлять код таких функций непосредственно в точку их вызова
. С одной стороны, это увеличивает объём исполняемого кода, но, с другой, — позволяет экономить время его выполнения, поскольку не используется дорогостоящая по времени операция вызова функции. Однако из-за особенностей построения архитектуры компьютеров, встраивание функций может приводить как к ускорению, так и к замедлению работы приложения в целом. Тем не менее во многих случаях встраиваемые функции являются предпочтительной заменой макросам
.
Объявление функции имеет следующий формат:
(
[список]
);
,
где
void
при их отсутствии
.
Признаком объявления функции является символ «
;
», таким образом, объявление функции — это инструкция.
В самом простом случае [описатель] содержит указание на конкретный тип возвращаемого значения. Функция, которая не должна возвращать никакого значения, объявляется как имеющая тип
void
.
При необходимости в описателе могут присутствовать модификаторы, задаваемые с помощью ключевых слов:
extern
указывает на то, что определение функции находится в другом модуле
;
static
задаёт статическую функцию, которая может быть использована только в текущем модуле.
Список параметров функции задаёт сигнатуру функции.
Си не допускает объявление нескольких функций, имеющих одно и то же имя, перегрузка функций не поддерживается .
Определение функции имеет следующий формат:
(
[список]
)
[тело]
Где [описатель], [имя] и [список] — те же, что и в объявлении, а [тело] — это составной оператор, который представляет собою конкретную реализацию функции. Компилятор различает определения одноимённых функций по их сигнатуре, и таким образом (по сигнатуре) устанавливается связь между определением и соответствующим ему объявлением.
Тело функции имеет следующий вид:
{
return
([возвращаемое значение])
;
}
Возврат из функции осуществляется с помощью оператора
return
, у которого либо указывается возвращаемое значение, либо не указывается, в зависимости от возвращаемого функцией типа данных. В редких случаях функция может быть помечена как не делающая возврат с помощью макроса
noreturn
из заголовочного файла
stdnoreturn.h
, в таких случаях оператор
return
не требуется. Например, подобным образом можно помечать функции, безусловно вызывающие внутри себя
abort()
.
Вызов функции заключается в выполнении следующих действий:
В зависимости от реализации, компилятор либо строго следит за тем, чтобы тип фактического параметра совпадал с типом формального параметра, либо, если существует такая возможность, осуществляет неявное преобразование типа, что, очевидно, приводит к побочным эффектам.
Если в функцию передаётся переменная, то при вызове функции создаётся её копия (в стеке выделяется память и копируется значение). Например, передача структуры в функцию вызовет копирование всей структуры целиком. Если же передаётся указатель на структуру, то копируется только значение указателя. Передача в функцию массива также вызывает лишь копирование указателя на его первый элемент. При этом для явного обозначения того, что на вход функции принимается адрес начала массива, а не указатель на единичную переменную, вместо объявления указателя после названия переменной можно поставить квадратные скобки, например:
void example_func(int array[]); // array — указатель на первый элемент массива типа int
Си допускает вложенные вызовы. Глубина вложенности вызовов имеет очевидное ограничение, связанное с размером выделяемого программе стека. Поэтому в реализациях Си устанавливается некое предельное значение для глубины вложенности.
Частный случай вложенного вызова — это вызов функции внутри тела вызываемой функции. Такой вызов называется рекурсивным, и применяется для организации единообразных вычислений. Учитывая естественное ограничение на вложенные вызовы, рекурсивную реализацию заменяют на реализацию при помощи циклов.
bool
из заголовочного файла
stdbool.h
является обёрткой над ключевым словом
_Bool
.
complex
из заголовочного файла
complex.h
является обёрткой над ключевым словом
_Complex
.
imaginary
из заголовочного файла
complex.h
является обёрткой над ключевым словом
_Imaginary
.
alignas
из заголовочного файла
является обёрткой над ключевым словом
_Alignas
.
alignof
из заголовочного файла
является обёрткой над ключевым словом
_Alignof
.
noreturn
из заголовочного файла
stdnoreturn.h
является обёрткой над ключевым словом
_Noreturn
.
static_assert
из заголовочного файла
assert.h
является обёрткой над ключевым словом
_Static_assert
.
thread_local
из заголовочного файла
threads.h
является обёрткой над ключевым словом
_Thread_local
.