В этой статье есть несколько проблем. Помогите улучшить ее или обсудите эти проблемы на странице обсуждения . ( Узнайте, как и когда удалять эти сообщения )
|
Синтаксис языка программирования C — это набор правил, регулирующих написание программного обеспечения на языке C. Он разработан для того, чтобы программы были предельно лаконичными, имели тесную связь с результирующим объектным кодом и при этом обеспечивали относительно высокоуровневую абстракцию данных . C был первым широко успешным языком высокого уровня для разработки переносимых операционных систем .
Синтаксис языка C использует принцип максимального поглощения .
Язык программирования C представляет числа в трех формах: целочисленной , действительной и комплексной . Это различие отражает аналогичные различия в архитектуре набора инструкций большинства центральных процессоров . Целочисленные типы данных хранят числа в наборе целых чисел , тогда как действительные и комплексные числа представляют числа (или пары чисел) в наборе действительных чисел в форме с плавающей точкой .
Все целочисленные типы C имеют signed
и unsigned
варианты. Если signed
или unsigned
не указано явно, в большинстве случаев signed
предполагается, что . Однако по историческим причинам plain char
— это тип, отличный от signed char
и unsigned char
. Это может быть знаковый тип или беззнаковый тип в зависимости от компилятора и набора символов (C гарантирует, что члены базового набора символов C имеют положительные значения). Кроме того, типы битовых полей , указанные как plain, int
могут быть знаковыми или беззнаковыми в зависимости от компилятора.
Целочисленные типы C имеют различные фиксированные размеры, способные представлять различные диапазоны чисел. Тип char
занимает ровно один байт (наименьшая адресуемая единица хранения), которая обычно имеет ширину 8 бит. (Хотя char
может представлять любой из «базовых» символов C, для международных наборов символов может потребоваться более широкий тип.) Большинство целочисленных типов имеют как знаковые, так и беззнаковые разновидности, обозначаемые ключевыми словами signed
и unsigned
. Знаковые целочисленные типы всегда используют представление дополнения до двух , начиная с C23 [1] (и на практике до этого; в более старых версиях C до C23 представление могло бы альтернативно быть дополнением до единиц , или знаком и величиной , но на практике это не было сделано десятилетиями на современном оборудовании). Во многих случаях существует несколько эквивалентных способов обозначения типа; например, и являются синонимами.signed short int
short
Представление некоторых типов может включать неиспользуемые биты "заполнения", которые занимают память, но не включены в ширину. В следующей таблице представлен полный список стандартных целочисленных типов и их минимально допустимая ширина (включая любой знаковый бит).
Кратчайшая форма спецификатора | Минимальная ширина (бит) |
---|---|
_Bool | 1 |
char | 8 |
signed char | 8 |
unsigned char | 8 |
short | 16 |
unsigned short | 16 |
int | 16 |
unsigned int | 16 |
long | 32 |
unsigned long | 32 |
long long [примечание 1] | 64 |
unsigned long long [примечание 1] | 64 |
Тип char
отличается от обоих signed char
и unsigned char
, но гарантированно имеет то же представление, что и один из них. Типы _Bool
и long long
стандартизированы с 1999 года и могут не поддерживаться старыми компиляторами C. _Bool
Доступ к типу обычно осуществляется через typedef
имя bool
, определенное стандартным заголовком stdbool.h
.
В общем, ширина и схема представления, реализованные для любой данной платформы, выбираются на основе архитектуры машины, с некоторым учетом простоты импорта исходного кода, разработанного для других платформ. Ширина типа int
особенно сильно различается среди реализаций C; она часто соответствует наиболее «естественному» размеру слова для конкретной платформы. Стандартный заголовок limits.h определяет макросы для минимальных и максимальных представимых значений стандартных целочисленных типов, реализованных на любой конкретной платформе.
В дополнение к стандартным целочисленным типам могут быть и другие "расширенные" целочисленные типы, которые могут использоваться для typedef
s в стандартных заголовках. Для более точной спецификации ширины программисты могут и должны использовать typedef
s из стандартного заголовка stdint.h .
Целочисленные константы могут быть указаны в исходном коде несколькими способами. Числовые значения могут быть указаны как десятичные (пример: 1022
), восьмеричные с нулем ( 0
) в качестве префикса ( 01776
), или шестнадцатеричные с 0x
(нулем x) в качестве префикса ( 0x3FE
). Символ в одинарных кавычках (пример: 'R'
), называемый "символьной константой", представляет значение этого символа в наборе символов выполнения с типом int
. За исключением символьных констант, тип целочисленной константы определяется шириной, необходимой для представления указанного значения, но всегда имеет ширину не менее int
. Это можно переопределить, добавив явный модификатор длины и/или знака; например, 12lu
имеет тип unsigned long
. Отрицательных целочисленных констант нет, но тот же эффект часто можно получить, используя унарный оператор отрицания " -
".
Перечислимый тип в C, указанный с помощью enum
ключевого слова и часто называемый просто "enum" (обычно произносится как / ˈ iː n ʌ m / EE -num или / ˈ iː n uː m / EE -noom ), — это тип, предназначенный для представления значений в ряде именованных констант. Каждая из перечислимых констант имеет тип int
. Каждый enum
тип сам по себе совместим с char
целочисленным типом со знаком или без знака, но каждая реализация определяет свои правила выбора типа.
Некоторые компиляторы предупреждают, если объекту с перечислимым типом присваивается значение, которое не является одной из его констант. Однако такому объекту можно присваивать любые значения в диапазоне их совместимого типа, и enum
константы можно использовать везде, где ожидается целое число. По этой причине enum
значения часто используются вместо #define
директив препроцессора для создания именованных констант. Такие константы, как правило, безопаснее использовать, чем макросы, поскольку они находятся в определенном пространстве имен идентификаторов.
Перечислимый тип объявляется с помощью enum
спецификатора и необязательного имени (или тега ) для перечисления, за которым следует список из одной или нескольких констант, заключенных в фигурные скобки и разделенных запятыми, и необязательный список имен переменных. Последующие ссылки на определенный перечислимый тип используют ключевое enum
слово и имя перечисления. По умолчанию первой константе в перечислении присваивается значение ноль, а каждое последующее значение увеличивается на единицу по сравнению с предыдущей константой. Конкретные значения также могут быть назначены константам в объявлении, и любые последующие константы без определенных значений будут получать увеличенные значения с этого момента. Например, рассмотрим следующее объявление:
enum colors { КРАСНЫЙ , ЗЕЛЕНЫЙ , СИНИЙ = 5 , ЖЕЛТЫЙ } paint_color ;
Это объявляет enum colors
тип; int
константы RED
(чье значение равно 0), GREEN
(чье значение на единицу больше RED
, 1), BLUE
(чье значение равно заданному значению, 5), и YELLOW
(чье значение на единицу больше BLUE
, 6); и enum colors
переменную paint_color
. Константы могут использоваться вне контекста enum
(где разрешено любое целочисленное значение), и значения, отличные от констант, могут быть назначены paint_color
, или любой другой переменной типа enum colors
.
Форма с плавающей точкой используется для представления чисел с дробным компонентом. Однако они не представляют большинство рациональных чисел точно; вместо этого они являются близким приближением. Существует три стандартных типа действительных значений, обозначаемых их спецификаторами (и с C23 еще три десятичных типа): одинарная точность ( float
), двойная точность ( double
) и двойная расширенная точность ( long double
). Каждый из них может представлять значения в другой форме, часто в одном из форматов с плавающей точкой IEEE .
Спецификаторы типа | Точность (десятичные цифры) | Диапазон экспоненты | ||
---|---|---|---|---|
Минимум | IEEE 754 | Минимум | IEEE 754 | |
float | 6 | 7.2 (24 бита) | ±37 | ±38 (8 бит) |
double | 10 | 15.9 (53 бита) | ±37 | ±307 (11 бит) |
long double | 10 | 34.0 (113 бит) | ±37 | ±4931 (15 бит) |
Константы с плавающей точкой могут быть записаны в десятичной системе счисления , например 1.23
. Десятичная научная система счисления может быть использована путем добавления e
или E
последующего десятичного показателя степени, также известного как нотация E , например 1.23e2
(которая имеет значение 1,23 × 10 2 = 123,0). Требуется либо десятичная точка, либо показатель степени (в противном случае число анализируется как целая константа). Шестнадцатеричные константы с плавающей точкой следуют аналогичным правилам, за исключением того, что они должны иметь префикс 0x
и использовать p
или P
для указания двоичного показателя степени, например 0xAp-2
(которая имеет значение 2,5, так как A h × 2 −2 = 10 × 2 −2 = 10 ÷ 4). Как десятичные, так и шестнадцатеричные константы с плавающей точкой могут иметь суффикс f
или F
для указания константы типа float
, l
(букву l
) или L
для указания типа long double
, или оставаться без суффикса для double
константы.
Стандартный заголовочный файл float.h
определяет минимальные и максимальные значения типов с плавающей точкой реализации float
, double
, и long double
. Он также определяет другие ограничения, которые имеют отношение к обработке чисел с плавающей точкой.
C23 вводит три дополнительных десятичных (в отличие от двоичных) вещественных типа с плавающей точкой: _Decimal32, _Decimal64 и _Decimal128.
Несмотря на это, основание системы счисления исторически было двоичным (основание 2), то есть числа вроде 1/2 или 1/4 являются точными, но не 1/10, 1/100 или 1/3. С десятичной плавающей точкой все те же числа являются точными плюс числа вроде 1/10 и 1/100, но все еще не например 1/3. Ни одна известная реализация не выбирает десятичное основание для ранее известных двоичных типов. Поскольку большинство компьютеров даже не имеют оборудования для десятичных типов, а те немногие, которые имеют (например, мэйнфреймы IBM начиная с IBM System z10 ), могут использовать явно десятичные типы.
Каждый объект имеет класс хранения. Это определяет в основном длительность хранения, которая может быть статической (по умолчанию для глобального), автоматической (по умолчанию для локального) или динамической (выделенной), вместе с другими функциями (связь и подсказка регистра).
Спецификаторы | Продолжительность жизни | Объем | Инициализатор по умолчанию |
---|---|---|---|
auto | Блок (стек) | Блокировать | Неинициализированный |
register | Блок (стек или регистр ЦП) | Блокировать | Неинициализированный |
static | Программа | Блок или единица компиляции | Ноль |
extern | Программа | Глобальный (вся программа) | Ноль |
_Thread_local | Нить | ||
(нет) 1 | Динамический (куча) | Неинициализировано (инициализируется 0 при использовании calloc() ) |
malloc()
и free()
.Переменные, объявленные внутри блока по умолчанию, имеют автоматическое хранение, как и те, которые явно объявлены с помощью [примечание 2] или спецификаторов класса хранения. Спецификаторы и могут использоваться только внутри функций и объявлений аргументов функций; как таковой спецификатор всегда избыточен. Объекты, объявленные вне всех блоков, и те, которые явно объявлены с помощью спецификатора класса хранения, имеют статическую продолжительность хранения. Статические переменные по умолчанию инициализируются компилятором нулем .auto
register
auto
register
auto
static
Объекты с автоматическим хранилищем являются локальными для блока, в котором они были объявлены, и отбрасываются при выходе из блока. Кроме того, объекты, объявленные с register
классом хранилища , могут получить более высокий приоритет от компилятора для доступа к регистрам ; хотя компилятор может решить не сохранять ни один из них в регистре. Объекты с этим классом хранилища не могут использоваться с унарным оператором address-of ( &
). Объекты со статическим хранилищем сохраняются в течение всего времени выполнения программы. Таким образом, один и тот же объект может быть доступен функции через несколько вызовов. Объекты с выделенной продолжительностью хранения создаются и уничтожаются явно с помощью malloc
, free
, и связанных функций.
Спецификатор extern
класса хранения указывает, что хранилище для объекта было определено в другом месте. При использовании внутри блока он указывает, что хранилище было определено объявлением вне этого блока. При использовании вне всех блоков он указывает, что хранилище было определено вне единицы компиляции. Спецификатор extern
класса хранения является избыточным при использовании в объявлении функции. Он указывает, что объявленная функция была определена вне единицы компиляции.
Спецификатор класса хранения _Thread_local
( thread_local
в C++ , и в C начиная с C23 , и в более ранних версиях C, если заголовок <threads.h>
включен), введенный в C11 , используется для объявления локальной переменной потока. Его можно комбинировать с static
или extern
для определения связи.
Обратите внимание, что спецификаторы хранения применяются только к функциям и объектам; другие вещи, такие как объявления типов и перечислений, являются частными для единицы компиляции, в которой они появляются. Типы, с другой стороны, имеют квалификаторы (см. ниже).
Типы могут быть квалифицированы для указания особых свойств их данных. Квалификатор типа const
указывает, что значение не изменяется после инициализации. Попытка изменить const
квалифицированное значение приводит к неопределенному поведению, поэтому некоторые компиляторы C сохраняют их в rodata или (для встроенных систем) в постоянной памяти (ROM). Квалификатор типа volatile
указывает оптимизирующему компилятору , что он не может удалить явно избыточные чтения или записи, поскольку значение может измениться, даже если оно не было изменено каким-либо выражением или оператором, или может потребоваться несколько записей, например, для отображенного в память ввода-вывода .
Неполный тип — это тип структуры или объединения, члены которого еще не указаны, тип массива, размерность которого еще не указана, или void
тип ( void
тип не может быть завершен). Такой тип не может быть инстанцирован (его размер неизвестен), и к его членам нельзя получить доступ (они тоже неизвестны); однако, производный тип указателя может быть использован (но не разыменован).
Они часто используются с указателями, как в качестве предварительных, так и внешних деклараций. Например, код может объявить неполный тип следующим образом:
структура вещь * pt ;
Это объявляет pt
как указатель на struct thing
и неполный тип struct thing
. Указатели на данные всегда имеют одинаковую байтовую ширину независимо от того, на что они указывают, поэтому это утверждение само по себе допустимо (пока pt
не разыменовано). Неполный тип может быть завершен позже в той же области видимости путем его повторного объявления:
struct thing { int num ; }; /* тип структуры thing теперь завершен */
Неполные типы используются для реализации рекурсивных структур; тело объявления типа может быть отложено на более позднее время в единице трансляции:
typedef struct Берт Берт ; typedef struct Вилма Вилма ; структура Берт { Вилма * вилма ; }; структура Вильма { Берт * берт ; };
Неполные типы также используются для сокрытия данных ; неполный тип определяется в заголовочном файле , а тело — только в соответствующем исходном файле.
В объявлениях модификатор звездочка ( *
) указывает тип указателя. Например, там, где спецификатор int
ссылается на целочисленный тип, спецификатор int*
ссылается на тип «указатель на целое число». Значения указателя связывают две части информации: адрес памяти и тип данных. Следующая строка кода объявляет переменную указателя на целое число с именем ptr :
int * ptr ;
Когда объявляется нестатический указатель, с ним связано неопределенное значение. Адрес, связанный с таким указателем, должен быть изменен путем присваивания перед его использованием. В следующем примере ptr устанавливается так, чтобы он указывал на данные, связанные с переменной a :
int a = 0 ; int * ptr = &a a ;
Для этого &
используется оператор "address-of" (унарный). Он создает местоположение памяти объекта данных, который следует за ним.
Доступ к указанным данным можно получить через значение указателя. В следующем примере целочисленная переменная b устанавливается в значение целочисленной переменной a , которое равно 10:
целое а = 10 ; целое * р ; р = &а a ; int b = * p ;
Для выполнения этой задачи используется унарный оператор разыменования , обозначаемый звездочкой (*). Он возвращает данные, на которые указывает его операнд, который должен иметь тип указателя. Таким образом, выражение * p обозначает то же значение, что и a . Разыменование нулевого указателя является незаконным.
Массивы используются в C для представления структур последовательных элементов одного типа. Определение массива (фиксированного размера) имеет следующий синтаксис:
массив целых чисел [ 100 ];
который определяет массив с именем array для хранения 100 значений примитивного типа int
. Если объявлено внутри функции, размерность массива также может быть неконстантным выражением, в этом случае будет выделена память для указанного количества элементов. В большинстве контекстов при дальнейшем использовании упоминание переменной array преобразуется в указатель на первый элемент массива. sizeof
Оператор является исключением: sizeof array
возвращает размер всего массива (то есть в 100 раз больше размера int
, и sizeof(array) / sizeof(int)
вернет 100). Другим исключением является оператор & (address-of), который возвращает указатель на весь массив, например
int ( * ptr_to_array )[ 100 ] = & массив ;
Первичным средством доступа к значениям элементов массива является оператор индексации массива. Для доступа к i- индексированному элементу массива синтаксис будет таким array[i]
: , что ссылается на значение, хранящееся в этом элементе массива.
Нумерация индексов массива начинается с 0 (см. Индексация с нуля ). Таким образом, максимальный допустимый индекс массива равен количеству элементов в массиве минус 1. Чтобы проиллюстрировать это, рассмотрим массив a, объявленный как имеющий 10 элементов; первый элемент будет a[0]
, а последний элемент будет a[9]
.
C не предоставляет возможности для автоматической проверки границ для использования массива. Хотя логически последний индекс в массиве из 10 элементов будет 9, индексы 10, 11 и т. д. могут быть указаны случайно, с неопределенными результатами.
Поскольку массивы и указатели взаимозаменяемы, адреса каждого из элементов массива могут быть выражены в эквивалентной арифметике указателей . Следующая таблица иллюстрирует оба метода для существующего массива:
Элемент | Первый | Второй | Третий | н- й |
---|---|---|---|---|
Индекс массива | array[0] | array[1] | array[2] | array[n - 1] |
Разыменованный указатель | *array | *(array + 1) | *(array + 2) | *(array + n - 1) |
Поскольку выражение a[i]
семантически эквивалентно *(a+i)
, которое в свою очередь эквивалентно *(i+a)
, выражение также можно записать как i[a]
, хотя эта форма используется редко.
C99 стандартизировал массивы переменной длины (VLA) в области действия блока. Такие переменные массива выделяются на основе значения целочисленного значения во время выполнения при входе в блок и освобождаются в конце блока. [3] Начиная с C11 эта функция больше не требуется для реализации компилятором.
int n = ...; int a [ n ]; a [ 3 ] = 10 ;
Этот синтаксис создает массив, размер которого фиксирован до конца блока.
Массивы, которые можно динамически изменять в размере, можно создавать с помощью стандартной библиотеки C. Функция malloc
предоставляет простой метод выделения памяти. Она принимает один параметр: объем памяти для выделения в байтах. После успешного выделения malloc
возвращает универсальное void
значение указателя ( ), указывающее на начало выделенного пространства. Возвращаемое значение указателя преобразуется в соответствующий тип неявно путем присваивания. Если выделение не удалось завершить, malloc
возвращает нулевой указатель . Таким образом, следующий сегмент по своей функции аналогичен указанному выше желаемому объявлению:
#include <stdlib.h> /* объявляет malloc */ ... int * a = malloc ( n * sizeof * a ); a [ 3 ] = 10 ;
Результатом является "указатель на int
" переменную ( a ), которая указывает на первый из n смежных int
объектов; из-за эквивалентности массив-указатель это может быть использовано вместо фактического имени массива, как показано в последней строке. Преимущество использования этого динамического выделения заключается в том, что объем памяти, выделяемой для него, может быть ограничен тем, что фактически необходимо во время выполнения, и это может быть изменено по мере необходимости (с помощью стандартной библиотечной функции realloc
).
Когда динамически выделенная память больше не нужна, ее следует освободить обратно в систему выполнения. Это делается с помощью вызова функции free
. Она принимает один параметр: указатель на ранее выделенную память. Это значение, которое было возвращено предыдущим вызовом malloc
.
В качестве меры безопасности некоторые программисты [ кто? ] затем устанавливают переменную-указатель в NULL
:
свободно ( а ); а = NULL ;
Это гарантирует, что дальнейшие попытки разыменовать указатель на большинстве систем приведут к сбою программы. Если этого не сделать, переменная станет висячим указателем, что может привести к ошибке использования после освобождения. Однако, если указатель является локальной переменной, установка его в NULL
не помешает программе использовать другие копии указателя. Локальные ошибки использования после освобождения обычно легко распознаются статическими анализаторами . Поэтому этот подход менее полезен для локальных указателей и чаще используется с указателями, хранящимися в долгоживущих структурах. В целом, однако, установка указателей в NULL
является хорошей практикой [ по мнению кого? ], поскольку это позволяет программисту проверять NULL
указатели перед разыменованием, тем самым помогая предотвратить сбои.
Возвращаясь к примеру с массивом, можно также создать массив фиксированного размера с помощью динамического выделения памяти:
int ( * a )[ 100 ] = malloc ( sizeof * a );
...Что дает указатель на массив.
Доступ к указателю на массив можно осуществить двумя способами:
( * а )[ индекс ];индекс [ * а ];
Итерацию также можно выполнить двумя способами:
для ( int i = 0 ; i < 100 ; i ++ ) ( * a )[ i ]; для ( int * i = a [ 0 ]; i < a [ 1 ]; i ++ ) * i ;
Преимущество использования второго примера заключается в том, что числовое ограничение первого примера не требуется, а это значит, что указатель на массив может быть любого размера, и второй пример может выполняться без каких-либо изменений.
Кроме того, C поддерживает массивы нескольких измерений, которые хранятся в порядке строк . Технически, многомерные массивы C — это просто одномерные массивы, элементами которых являются массивы. Синтаксис объявления многомерных массивов следующий:
int array2d [ СТРОКИ ][ СТОЛБЦЫ ];
где ROWS и COLUMNS — константы. Это определяет двумерный массив. Читая индексы слева направо, array2d — это массив длиной ROWS , каждый элемент которого — массив целых чисел COLUMNS .
Чтобы получить доступ к целочисленному элементу в этом многомерном массиве, можно использовать
массив2d [ 4 ][ 3 ]
Опять же, читая слева направо, мы получаем доступ к 5-й строке и 4-му элементу в этой строке. Выражение array2d[4]
представляет собой массив, который мы затем индексируем с помощью [3], чтобы получить доступ к четвертому целому числу.
Элемент | Первый | Вторая строка, второй столбец | i -я строка, j- й столбец |
---|---|---|---|
Индекс массива | array[0][0] | array[1][1] | array[i - 1][j - 1] |
Разыменованный указатель | *(*(array + 0) + 0) | *(*(array + 1) + 1) | *(*(array + i - 1) + j - 1) |
Аналогичным образом можно объявить многомерные массивы.
Многомерный массив не следует путать с массивом указателей на массивы (также известным как вектор Илиффе или иногда массив массивов ). Первый всегда прямоугольный (все подмассивы должны быть одинакового размера) и занимает непрерывную область памяти. Последний представляет собой одномерный массив указателей, каждый из которых может указывать на первый элемент подмассива в другом месте памяти, и подмассивы не обязательно должны быть одинакового размера. Последний может быть создан путем многократного использования malloc
.
В языке C строковые литералы заключаются в двойные кавычки ( "
) (например, "Hello world!"
) и компилируются в массив указанных char
значений с дополнительным кодом завершающего нулевого символа (со значением 0), обозначающим конец строки.
Строковые литералы не могут содержать встроенные символы новой строки; это ограничение несколько упрощает синтаксический анализ языка. Чтобы включить новую строку в строку, \n
можно использовать экранирование обратной косой черты, как показано ниже.
Существует несколько стандартных библиотечных функций для работы со строковыми данными (не обязательно константами), организованными в виде массива с char
использованием этого формата с завершающим нулем; см. ниже.
Синтаксис строковых литералов C оказал большое влияние и проник во многие другие языки, такие как C++, Objective-C, Perl, Python, PHP, Java, JavaScript, C# и Ruby. В настоящее время почти все новые языки принимают или строят на основе синтаксиса строк в стиле C. Языки, в которых отсутствует этот синтаксис, как правило, предшествуют C.
Поскольку некоторые символы не могут быть частью выражения строки литерала напрямую, они вместо этого идентифицируются escape-последовательностью, начинающейся с обратной косой черты ( \
). Например, обратные косые черты в "This string contains \"double quotes\"."
указывают (компилятору), что внутренняя пара кавычек предназначена как фактическая часть строки, а не как по умолчанию разделитель (конечная точка) самой строки.
Обратные косые черты можно использовать для ввода в строку различных управляющих символов и т. д.:
Побег | Значение |
---|---|
\\ | Буквальный обратный слеш |
\" | Двойная кавычка |
\' | Одинарная кавычка |
\n | Новая строка (перевод строки) |
\r | Возврат каретки |
\b | Возврат на одну позицию |
\t | Горизонтальная вкладка |
\f | Форма подачи |
\a | Сигнал тревоги (звонок) |
\v | Вертикальная вкладка |
\? | Вопросительный знак (используется для экранирования триграфов , устаревших функций, исключенных в C23) |
%% | Знак процента, только строки формата printf (Примечание: \% не является стандартным и не всегда распознается) |
\OOO | Символ с восьмеричным значением OOO (где OOO — это 1–3 восьмеричные цифры, '0'–'7') |
\xHH | Символ с шестнадцатеричным значением HH (где HH — это 1 или более шестнадцатеричных цифр, '0'-'9','A'-'F','a'-'f') |
Использование других экранированных символов обратной косой черты не определено стандартом C, хотя поставщики компиляторов часто предоставляют дополнительные экранированные коды в качестве языковых расширений. Одним из них является экранированная последовательность \e
для экранированного символа с шестнадцатеричным значением ASCII 1B, которая не была добавлена в стандарт C из-за отсутствия представления в других наборах символов (таких как EBCDIC ). Она доступна в GCC , clang и tcc .
В языке C реализована конкатенация строковых литералов , то есть соседние строковые литералы объединяются во время компиляции. Это позволяет разбивать длинные строки на несколько строк, а также позволяет добавлять строковые литералы, полученные в результате определений и макросов препроцессора C , к строкам во время компиляции:
printf ( __FILE__ ": %d: Привет " "мир \n " , __LINE__ );
будет расширяться до
printf ( "helloworld.c" ": %d: Привет " "мир \n " , 10 );
что синтаксически эквивалентно
printf ( "helloworld.c: %d: Привет, мир \n " , 10 );
Отдельные константы символов заключаются в одинарные кавычки, например 'A'
, и имеют тип int
(в C++ char
). Разница в том, что "A"
представляет собой массив из двух символов с нулевым завершением, 'A' и '\0', тогда как 'A'
представляет непосредственно значение символа (65, если используется ASCII). Поддерживаются те же экранированные обратные косые черты, что и для строк, за исключением того, что (конечно) "
может быть допустимо использовано как символ без экранирования, тогда как '
теперь должно быть экранировано.
Символьная константа не может быть пустой (т. е. ''
имеет недопустимый синтаксис), хотя строка может быть пустой (она все равно имеет завершающий нулевой символ). Многосимвольные константы (например, 'xy'
) допустимы, хотя редко полезны — они позволяют хранить несколько символов в целом числе (например, 4 символа ASCII могут поместиться в 32-битное целое число, 8 — в 64-битное). Поскольку порядок, в котором символы упакованы в , int
не указан (оставлен на усмотрение реализации), переносимое использование многосимвольных констант затруднено.
Тем не менее, в ситуациях, ограниченных конкретной платформой и реализацией компилятора, многосимвольные константы находят свое применение в указании сигнатур. Одним из распространенных вариантов использования является OSType , где сочетание классических компиляторов Mac OS и присущей им big-endianness означает, что байты в целом числе появляются в точном порядке символов, определенных в литерале. Определения популярных «реализаций» на самом деле согласованы: в GCC, Clang и Visual C++ выдает '1234'
под ASCII. [5] [6]0x31323334
Как и строковые литералы, символьные константы также могут быть изменены с помощью префиксов, например, L'A'
имеет тип wchar_t
и представляет значение символа «A» в расширенной кодировке символов.
Поскольку type char
имеет ширину 1 байт, одно char
значение обычно может представлять максимум 255 различных кодов символов, что недостаточно для всех символов, используемых во всем мире. Чтобы обеспечить лучшую поддержку международных символов, первый стандарт C (C89) ввел широкие символы (закодированные в type wchar_t
) и строки широких символов, которые записываются какL"Hello world!"
Широкие символы чаще всего имеют ширину 2 байта (используя 2-байтовую кодировку, такую как UTF-16 ) или 4 байта (обычно UTF-32 ), но стандарт C не определяет ширину для wchar_t
, оставляя выбор за разработчиком. Microsoft Windows обычно использует UTF-16, поэтому указанная выше строка будет иметь длину 26 байт для компилятора Microsoft; мир Unix предпочитает UTF-32, поэтому компиляторы, такие как GCC, сгенерируют 52-байтовую строку. 2-байтовая ширина wchar_t
страдает от того же ограничения char
, что и , в том смысле, что некоторые символы (те, что за пределами BMP ) не могут быть представлены в одном wchar_t
; но должны быть представлены с использованием суррогатных пар .
Первоначальный стандарт C определял только минимальные функции для работы с широкими строками символов; в 1995 году стандарт был изменен, чтобы включить гораздо более обширную поддержку, сравнимую со char
строками. Соответствующие функции в основном названы по их char
эквивалентам, с добавлением "w" или заменой "str" на "wcs"; они указаны в <wchar.h>
, с <wctype.h>
содержащими функциями классификации и отображения широких символов.
В настоящее время общерекомендуемым методом [примечание 3] поддержки международных символов является использование UTF-8 , который хранится в char
массивах и может быть записан непосредственно в исходном коде при использовании редактора UTF-8, поскольку UTF-8 является прямым расширением ASCII .
Распространенной альтернативой wchar_t
является использование кодировки переменной ширины , при которой логический символ может занимать несколько позиций строки. Строки переменной ширины могут быть закодированы в литералы дословно, с риском запутать компилятор, или с использованием числовых экранированных обратных косых черт (например, "\xc3\xa9"
для "é" в UTF-8). Кодировка UTF-8 была специально разработана (в рамках Plan 9 ) для совместимости со стандартными библиотечными строковыми функциями; вспомогательные функции кодировки включают отсутствие встроенных нулей, отсутствие допустимых интерпретаций для подпоследовательностей и тривиальную повторную синхронизацию. Кодировки, в которых отсутствуют эти функции, скорее всего, окажутся несовместимыми со стандартными библиотечными функциями; в таких случаях часто используются строковые функции, учитывающие кодировку.
Строки , как константы, так и переменные, можно обрабатывать без использования стандартной библиотеки . Однако библиотека содержит много полезных функций для работы со строками, завершающимися нулем.
Структуры и объединения в C определяются как контейнеры данных, состоящие из последовательности именованных членов различных типов. Они похожи на записи в других языках программирования. Члены структуры хранятся в последовательных местах в памяти, хотя компилятору разрешено вставлять заполнение между или после членов (но не перед первым членом) для эффективности или в качестве заполнения, необходимого для правильного выравнивания целевой архитектурой. Размер структуры равен сумме размеров ее членов плюс размер заполнения.
Объединения в C связаны со структурами и определяются как объекты, которые могут содержать (в разное время) объекты разных типов и размеров. Они аналогичны вариантам записей в других языках программирования. В отличие от структур, все компоненты объединения ссылаются на одно и то же место в памяти. Таким образом, объединение может использоваться в разное время для хранения разных типов объектов, без необходимости создания отдельного объекта для каждого нового типа. Размер объединения равен размеру его наибольшего типа компонента.
Структуры объявляются с помощью struct
ключевого слова , а объединения объявляются с помощью union
ключевого слова . За ключевым словом спецификатора следует необязательное имя идентификатора, которое используется для идентификации формы структуры или объединения. За идентификатором следует объявление тела структуры или объединения: список объявлений членов, заключенных в фигурные скобки, каждое объявление завершается точкой с запятой. Наконец, объявление завершается необязательным списком имен идентификаторов, которые объявляются как экземпляры структуры или объединения.
Например, следующий оператор объявляет структуру с именем s
, содержащую три члена; он также объявляет экземпляр структуры, известный как tee
:
структура s { int x ; float y ; char * z ; } tee ;
А следующий оператор объявит аналогичное объединение с именем u
и его экземпляр с именем n
:
объединение u { int x ; float y ; char * z ; } n ;
Члены структур и объединений не могут иметь неполный или функциональный тип. Таким образом, члены не могут быть экземпляром объявляемой структуры или объединения (потому что они неполны в этой точке), но могут быть указателями на объявляемый тип.
После того, как структура или тело объединения были объявлены и получили имя, их можно считать новым типом данных, используя спецификатор struct
или union
, в зависимости от ситуации, и имя. Например, следующий оператор, учитывая приведенное выше объявление структуры, объявляет новый экземпляр структуры s
с именем r
:
структура s r ;
Также распространено использование typedef
спецификатора для устранения необходимости в ключевом слове struct
or union
в последующих ссылках на структуру. Первый идентификатор после тела структуры принимается как новое имя для типа структуры (экземпляры структуры не могут быть объявлены в этом контексте). Например, следующий оператор объявит новый тип, известный как s_type , который будет содержать некоторую структуру:
typedef struct {...} s_type ;
В будущих операторах для ссылки на структуру можно использовать спецификатор s_type (вместо расширенного спецификатора ...).struct
Доступ к членам осуществляется с использованием имени экземпляра структуры или объединения, точки ( .
) и имени члена. Например, учитывая объявление tee выше, к члену, известному как y (типа float
), можно получить доступ с использованием следующего синтаксиса:
тройник . y
Структуры обычно доступны через указатели. Рассмотрим следующий пример, который определяет указатель на tee , известный как ptr_to_tee :
структура s * ptr_to_tee = & тройник ;
Затем к члену y класса tee можно получить доступ, разыменовав ptr_to_tee и используя результат в качестве левого операнда:
( * ptr_to_tee ). y
Что идентично более простому варианту tee.y
выше, пока ptr_to_tee указывает на tee . Из-за приоритета оператора ("." выше, чем "*"), более короткий вариант *ptr_to_tee.y
некорректен для этой цели, вместо этого он анализируется как *(ptr_to_tee.y)
, и поэтому скобки необходимы. Поскольку эта операция является распространенной, C предоставляет сокращенный синтаксис для доступа к члену напрямую из указателя. С этим синтаксисом имя экземпляра заменяется именем указателя, а точка заменяется последовательностью символов ->
. Таким образом, следующий метод доступа к y идентичен предыдущим двум:
ptr_to_tee -> y
Аналогичным образом осуществляется доступ к членам профсоюзов.
Это можно объединить в цепочку; например, в связанном списке можно ссылаться на n->next->next
второй следующий узел (предполагая, что он n->next
не равен нулю).
Присвоение значений отдельным членам структур и объединений синтаксически идентично присвоению значений любому другому объекту. Единственное отличие состоит в том, что lvalue назначения — это имя члена, доступ к которому осуществляется с помощью синтаксиса, упомянутого выше.
Структура также может быть назначена как единица другой структуре того же типа. Структуры (и указатели на структуры) также могут использоваться как параметры функций и возвращаемые типы.
Например, следующий оператор присваивает значение 74 (код ASCII для буквы «t») члену с именем x в структуре tee , приведенной выше:
тройник . х = 74 ;
И то же самое назначение, использующее ptr_to_tee вместо tee , будет выглядеть так:
ptr_to_tee -> x = 74 ;
Назначение членов профсоюзов идентично.
Согласно стандарту C, единственными допустимыми операциями, которые можно выполнять над структурой, являются ее копирование, назначение ей как единице (или инициализация), получение ее адреса с помощью &
унарного оператора address-of ( ) и доступ к ее членам. Объединения имеют те же ограничения. Одной из неявно запрещенных операций является сравнение: структуры и объединения нельзя сравнивать с помощью стандартных средств сравнения C ( ==
, >
, <
, и т. д.).
C также предоставляет специальный тип члена, известный как битовое поле , которое является целым числом с явно указанным числом битов. Битовое поле объявляется как член структуры (или объединения) типа int
, signed int
, unsigned int
, или _Bool
, [примечание 4] после имени члена следует двоеточие ( :
) и число битов, которые оно должно занимать. Общее число битов в одном битовом поле не должно превышать общее число битов в его объявленном типе (однако это разрешено в C++, где дополнительные биты используются для заполнения).
В качестве особого исключения из обычных правил синтаксиса C, реализация определяет, является ли битовое поле int
, объявленное как type без указания signed
or unsigned
, знаковым или беззнаковым. Таким образом, рекомендуется явно указывать signed
or unsigned
для всех членов структуры для переносимости.
Также разрешены неименованные поля, состоящие только из двоеточия, за которым следует несколько битов; они указывают на заполнение . Указание нулевой ширины для неименованного поля используется для принудительного выравнивания по новому слову. [7] Поскольку все члены объединения занимают одну и ту же память, неименованные битовые поля нулевой ширины ничего не делают в объединениях, однако неименованные битовые поля ненулевой ширины могут изменить размер объединения, поскольку они должны в него вписаться.
Члены битовых полей не имеют адресов и, как таковые, не могут использоваться с &
унарным оператором address-of ( ). sizeof
Оператор не может применяться к битовым полям.
Следующее объявление объявляет новый тип структуры, известный как f
и его экземпляр, известный как g
. Комментарии содержат описание каждого из членов:
struct f { unsigned int flag : 1 ; /* битовый флаг: может быть включен (1) или выключен (0) */ signed int num : 4 ; /* 4-битное поле со знаком; диапазон -7...7 или -8...7 */ signed int : 3 ; /* 3 бита заполнения для округления до 8 бит */ } g ;
Инициализация по умолчанию зависит от спецификатора класса хранения, описанного выше.
Из-за грамматики языка скалярный инициализатор может быть заключен в любое количество пар фигурных скобок. Однако большинство компиляторов выдают предупреждение, если таких пар больше одной.
int x = 12 ; int y = { 23 }; // Законно, без предупреждения int z = { { 34 } }; // Законно, ожидайте предупреждения
Структуры, объединения и массивы могут быть инициализированы в их объявлениях с помощью списка инициализаторов. Если не используются позиционеры, компоненты инициализатора соответствуют элементам в порядке их определения и сохранения, поэтому все предыдущие значения должны быть предоставлены до значения любого конкретного элемента. Любые неуказанные элементы устанавливаются в ноль (за исключением объединений). Упоминание слишком большого количества значений инициализации приводит к ошибке.
Следующий оператор инициализирует новый экземпляр структуры s, известный как pi :
структура s { int x ; float y ; char * z ; }; структура s pi = { 3 , 3.1415 , "Пи" };
Назначенные инициализаторы позволяют инициализировать элементы по имени, в любом порядке и без явного предоставления предыдущих значений. Следующая инициализация эквивалентна предыдущей:
структура s pi = { . z = "Пи" , . x = 3 , . y = 3.1415 };
Использование указателя в инициализаторе перемещает «курсор» инициализации. В примере ниже, если MAX
больше 10, в середине будут некоторые нулевые элементы a
; если меньше 10, некоторые значения, предоставленные первыми пятью инициализаторами, будут переопределены вторыми пятью (если MAX
меньше 5, возникнет ошибка компиляции):
int a [ МАКС ] = { 1 , 3 , 5 , 7 , 9 , [ МАКС -5 ] = 8 , 6 , 4 , 2 , 0 };
В C89 объединение инициализировалось одним значением, примененным к его первому члену. То есть, объединение u, определенное выше, могло иметь только инициализированным свой член int x :
значение объединения u = { 3 };
При использовании назначенного инициализатора инициализируемый член не обязательно должен быть первым членом:
значение объединения u = { . y = 3,1415 };
Если размер массива неизвестен (т.е. массив имеет неполный тип), количество инициализаторов определяет размер массива, и его тип становится полным:
int x [] = { 0 , 1 , 2 } ;
Составные указатели могут использоваться для предоставления явной инициализации, когда неукрашенные списки инициализаторов могут быть неправильно поняты. В примере ниже w
объявлен как массив структур, каждая структура состоит из члена a
(массив из 3 int
) и члена b
( int
). Инициализатор устанавливает размер w
равным 2 и устанавливает значения первого элемента каждого a
:
структура { int a [ 3 ], b ; } w [] = { [ 0 ]. a = { 1 }, [ 1 ]. a [ 0 ] = 2 };
Это эквивалентно:
структура { int a [ 3 ], b ; } w [] = { { { 1 , 0 , 0 }, 0 }, { { 2 , 0 , 0 }, 0 } };
В стандартном языке C нет способа указать повторение инициализатора.
Можно заимствовать методологию инициализации для генерации составных структур и литералов массивов:
// указатель, созданный из литерала массива. int * ptr = ( int []){ 10 , 20 , 30 , 40 }; // указатель на массив. float ( * foo )[ 3 ] = & ( float []){ 0.5f , 1.f , -0.5f }; структура s пи = ( структура s ){ 3 , 3.1415 , "Пи" };
Составные литералы часто объединяются с назначенными инициализаторами, чтобы сделать объявление более читабельным: [3]
pi = ( struct s ) { . z = "Пи" , . x = 3 , . y = 3,1415 };
C — язык свободной формы .
Стиль скобок варьируется от программиста к программисту и может быть предметом споров. Подробнее см. в разделе Стиль отступа .
В пунктах этого раздела любой <statement> может быть заменен составным оператором . Составные операторы имеют вид:
{ < необязательно - объявление - список > < необязательно - оператор - список > }
и используются как тело функции или в любом месте, где ожидается один оператор. Список объявлений объявляет переменные, которые будут использоваться в этой области , а список операторов — действия, которые должны быть выполнены. Скобки определяют свою собственную область действия, а переменные, определенные внутри этих скобок, будут автоматически освобождены в закрывающей скобке. Объявления и операторы можно свободно смешивать внутри составного оператора (как в C++ ).
В языке C есть два типа операторов выбора : if
оператор и switch
оператор .
Заявление if
имеет вид:
если ( < выражение > ) < оператор1 > иначе < оператор2 >
В if
операторе, если <expression>
в скобках ненулевое значение (истина), управление переходит к <statement1>
. Если else
предложение присутствует и <expression>
равно нулю (ложь), управление перейдет к <statement2>
. else <statement2>
Часть необязательна, и если отсутствует, ложь <expression>
просто приведет к пропуску <statement1>
. else
Всегда соответствует ближайшему предыдущему несовпавшему if
; фигурные скобки могут использоваться для переопределения этого при необходимости или для ясности.
Оператор switch
вызывает передачу управления одному из нескольких операторов в зависимости от значения выражения , которое должно иметь целочисленный тип . Подоператор, управляемый переключателем, обычно является составным. Любой оператор внутри подоператор может быть помечен одной или несколькими case
метками, которые состоят из ключевого слова, case
за которым следует константное выражение и двоеточие (:). Синтаксис следующий:
switch ( < выражение > ) { case < метка1 > : < операторы 1 > case < метка2 > : < операторы 2 > break ; default : < операторы 3 > }
Никакие две константы case, связанные с одним и тем же switch, не могут иметь одинаковое значение. default
С switch может быть связана максимум одна метка. Если ни одна из меток case не равна выражению в скобках после switch
, управление передается default
метке или, если метки нет default
, выполнение возобновляется сразу за всей конструкцией.
Switch могут быть вложенными; метка case
or default
связана с самым внутренним switch
, который ее содержит. Операторы Switch могут «проваливаться», то есть, когда один раздел case завершил свое выполнение, операторы будут продолжать выполняться вниз, пока не break;
встретится оператор. Проваливание полезно в некоторых обстоятельствах, но обычно нежелательно. В предыдущем примере, если достигнуто, выполняются <label2>
операторы и больше ничего внутри фигурных скобок. Однако, если достигнуто, выполняются и , поскольку нет no для разделения двух операторов case.<statements 2>
<label1>
<statements 1>
<statements 2>
break
Возможно, хотя и необычно, вставлять switch
метки в подблоки других структур управления. Примерами этого являются устройство Даффа и реализация сопрограмм Саймона Тэтхэма в Putty . [8]
В языке C есть три формы оператора итерации :
do < оператор > while ( < выражение > ) ; в то время как ( < выражение > ) < оператор > для ( < выражение > ; < выражение > ; < выражение > ) < оператор >
В операторах while
и do
подоператор выполняется многократно, пока значение expression
остается ненулевым (эквивалентно true). При использовании while
проверка, включая все побочные эффекты от <expression>
, происходит перед каждой итерацией (выполнением <statement>
); при использовании do
проверка происходит после каждой итерации. Таким образом, do
оператор всегда выполняет свой подоператор по крайней мере один раз, тогда как while
подоператор может вообще не выполняться.
Заявление:
для ( е1 ; е2 ; е3 ) с ;
эквивалентно:
e1 ; в то время как ( e2 ) { s ; продолжение : e3 ; }
за исключением поведения оператора continue;
(который в for
цикле переходит к e3
вместо e2
). Если e2
пусто, его нужно заменить на 1
.
Любое из трех выражений в for
цикле может быть опущено. Отсутствие второго выражения делает while
тест всегда ненулевым, создавая потенциально бесконечный цикл.
Начиная с C99 , первое выражение может иметь форму объявления, обычно включающего инициализатор, например:
для ( int i = 0 ; i < предел ; ++ i ) { // ... }
Область действия декларации ограничена пределами цикла for
.
Операторы перехода передают управление безусловно. В языке C есть четыре типа операторов перехода : goto
, continue
, break
, и return
.
Заявление goto
выглядит так:
перейти к < идентификатор > ;
Идентификатор должен быть меткой (с двоеточием), расположенной в текущей функции. Управление передается помеченному оператору .
Оператор continue
может появляться только внутри оператора итерации и приводит к передаче управления в часть продолжения цикла самого внутреннего охватывающего оператора итерации. То есть, внутри каждого из операторов
while ( выражение ) { /* ... */ продолжение : ; } do { /* ... */ продолжение : ; } while ( выражение ); для ( выражение1 ; выражение2 ; выражение3 ) { /* ... */ продолжение : ; }
не continue
содержащийся внутри вложенного оператора итерации, то же самое, что и goto cont
.
Оператор break
используется для завершения for
цикла, while
цикла, do
цикла или switch
оператора. Управление переходит к оператору, следующему за завершенным оператором.
Функция возвращает вызывающему оператору return
. Когда return
за ним следует выражение, значение возвращается вызывающему в качестве значения функции. Встреча с концом функции эквивалентна a return
без выражения. В этом случае, если функция объявлена как возвращающая значение, а вызывающий пытается использовать возвращаемое значение, результат не определен.
GCC расширяет язык C с помощью унарного &&
оператора, который возвращает адрес метки. Этот адрес может быть сохранен в void*
типе переменной и может быть использован позже в goto
инструкции. Например, следующее выводится "hi "
в бесконечном цикле:
void * ptr = && J1 ; J1 : printf ( "привет" ); goto * ptr ;
Эту функцию можно использовать для реализации таблицы переходов .
Определение функции AC состоит из возвращаемого типа ( void
если значение не возвращается), уникального имени, списка параметров в скобках и различных операторов:
< return - type > functionName ( < список параметров > ) { < операторы > return < выражение типа return - type > ; }
Функция с невозвращаемым void
типом должна включать по крайней мере один return
оператор. Параметры задаются <parameter-list>
списком объявлений параметров, разделенным запятыми, каждый элемент в списке является типом данных, за которым следует идентификатор: <data-type> <variable-identifier>, <data-type> <variable-identifier>, ...
.
Тип возвращаемого значения не может быть типом массива или типом функции.
int f ()[ 3 ]; // Ошибка: функция возвращает массив int ( * g ())[ 3 ]; // ОК: функция возвращает указатель на массив. void h ()(); // Ошибка: функция возвращает функцию void ( * k ())(); // ОК: функция возвращает указатель на функцию
Если параметры отсутствуют, то их <parameter-list>
можно оставить пустыми или указать одним словом void
.
Можно определить функцию как принимающую переменное число параметров, указав ...
ключевое слово в качестве последнего параметра вместо идентификатора переменной типа данных ad. Обычно используемая функция, которая делает это, — это стандартная библиотечная функция printf
, которая имеет следующее объявление:
int printf ( const char * , ...);
Манипулировать этими параметрами можно с помощью процедур в заголовке стандартной библиотеки <stdarg.h>
.
Указатель на функцию можно объявить следующим образом:
< возвращаемый тип > ( *< функция - имя > )( < параметр - список > ) ;
Следующая программа демонстрирует использование указателя функции для выбора между сложением и вычитанием:
#include <stdio.h> int ( * операция )( int x , int y ); int add ( int x , int y ) { return x + y ; } int вычитание ( int x , int y ) { вернуть x - y ; } int main ( int argc , char * args []) { int foo = 1 , bar = 1 ; операция = сложение ; printf ( "%d + %d = %d \n " , foo , bar , операция ( foo , bar )); операция = вычитание ; printf ( "%d - %d = %d \n " , foo , bar , операция ( foo , bar )); возврат 0 ; }
После предварительной обработки на самом высоком уровне программа на языке C состоит из последовательности объявлений в области действия файла. Они могут быть разделены на несколько отдельных исходных файлов, которые могут быть скомпилированы отдельно; полученные объектные модули затем связываются вместе с модулями поддержки времени выполнения, предоставляемыми реализацией, для создания исполняемого образа.
Декларации вводят функции , переменные и типы . Функции C похожи на подпрограммы Fortran или процедуры Pascal .
Определение — это особый тип объявления. Определение переменной выделяет хранилище и, возможно, инициализирует его, определение функции предоставляет его тело .
Реализация C, предоставляющая все стандартные библиотечные функции, называется размещенной реализацией . Программы, написанные для размещенных реализаций, должны определять специальную функцию, называемую main
, которая является первой функцией, вызываемой при начале выполнения программы.
Размещенные реализации начинают выполнение программы, вызывая main
функцию, которая должна быть определена в соответствии с одним из следующих прототипов (допускается использование разных имен параметров или разное написание типов):
int main () {...} int main ( void ) {...} int main ( int argc , char * argv []) {...} int main ( int argc , char ** argv ) {...} // char *argv[] и char **argv имеют тот же тип, что и параметры функции
Первые два определения эквивалентны (и оба совместимы с C++). Вероятно, это зависит от индивидуальных предпочтений, какое из них использовать (текущий стандарт C содержит два примера main()
и два примера main(void)
, но проект стандарта C++ использует main()
). Возвращаемое значение main
(которое должно быть int
) служит статусом завершения, возвращаемым в среду хоста.
Стандарт C определяет возвращаемые значения 0
и EXIT_SUCCESS
как указывающие на успех и EXIT_FAILURE
как указывающие на неудачу. ( EXIT_SUCCESS
и EXIT_FAILURE
определены в <stdlib.h>
). Другие возвращаемые значения имеют значения, определяемые реализацией; например, в Linux программа, завершенная сигналом , возвращает код возврата, равный числовому значению сигнала плюс 128.
Минимальная правильная программа на языке C состоит из пустой main
процедуры, не принимающей аргументов и ничего не делающей:
int main ( void ){}
Поскольку return
оператор отсутствует, main
при выходе возвращается 0. [3] (Это особая функция, введенная в C99 , которая применяется только к main
.)
Функция main
обычно вызывает другие функции, которые помогают ей выполнять свою работу.
Некоторые реализации не размещаются, обычно потому, что они не предназначены для использования с операционной системой . Такие реализации называются автономными в стандарте C. Автономная реализация может свободно указывать, как она обрабатывает запуск программы; в частности, ей не нужно требовать от программы определения функции main
.
Функции могут быть написаны программистом или предоставлены существующими библиотеками. Интерфейсы для последних обычно объявляются путем включения заголовочных файлов — с #include
директивой предварительной обработки — и объекты библиотеки связываются с конечным исполняемым образом. Некоторые библиотечные функции, такие как printf
, определены стандартом C; они называются стандартными библиотечными функциями.
Функция может возвращать значение вызывающей стороне (обычно другой функции C или среде размещения функции main
). printf
Упомянутая выше функция возвращает количество напечатанных символов, но это значение часто игнорируется.
В языке C аргументы передаются функциям по значению, тогда как другие языки могут передавать переменные по ссылке . Это означает, что принимающая функция получает копии значений и не имеет прямого способа изменить исходные переменные. Чтобы функция могла изменить переменную, переданную из другой функции, вызывающая сторона должна передать ее адрес ( указатель на нее), который затем может быть разыменован в принимающей функции. Для получения дополнительной информации см. Указатели.
void incInt ( int * y ) { ( * y ) ++ ; // Увеличиваем значение 'x' в 'main' ниже на единицу } int main ( void ) { int x = 0 ; incInt ( & x ); // передаем ссылку на var 'x' return 0 ; }
Функция scanf работает таким же образом:
int x ; scanf ( "%d" , & x );
Чтобы передать редактируемый указатель в функцию (например, для возврата выделенного массива в вызывающий код), необходимо передать указатель на этот указатель: его адрес.
#include <stdio.h> #include <stdlib.h> void allocate_array ( int ** const a_p , const int A ) { /* выделить массив целых чисел A, присваивание *a_p изменяет 'a' в main() */ * a_p = malloc ( sizeof ( int ) * A ); } int main ( void ) { int * a ; /* создаем указатель на одно или несколько целых чисел, это будет массив */ /* передать адрес 'a' */ allocate_array ( &a a , 42 ); /* 'a' теперь является массивом длиной 42 и может быть изменен и освобожден здесь */ бесплатно ( а ); вернуть 0 ; }
Параметр int **a_p
представляет собой указатель на указатель на int
, который в данном случае является адресом указателя, p
определенного в основной функции.
Параметры функции типа массива на первый взгляд могут показаться исключением из правила передачи по значению в языке C. Следующая программа выведет 2, а не 1:
#include <stdio.h> void setArray ( массив int [], индекс int , значение int ) { массив [ индекс ] = значение ; } int main ( void ) { int a [ 1 ] = {1} ; setArray ( a , 0,2 ) ; printf ( " a[0]=%d \n " , a [ 0 ]) ; return 0 ; }
Однако есть и другая причина такого поведения. Фактически, параметр функции, объявленный с типом массива, рассматривается как параметр, объявленный как указатель. То есть предыдущее объявление setArray
эквивалентно следующему:
void setArray ( int * array , int index , int value )
В то же время правила языка C по использованию массивов в выражениях приводят к тому, что значение a
в вызове setArray
преобразуется в указатель на первый элемент массива a
. Таким образом, по сути это все еще пример передачи по значению, с той оговоркой, что по значению передается адрес первого элемента массива, а не содержимое массива.
Начиная с C99, программист может указать, что функция принимает массив определенного размера, используя ключевое слово static
. В void setArray(int array[static 4], int index, int value)
первом параметре должен быть указатель на первый элемент массива длиной не менее 4. Также можно добавлять квалификаторы ( const
, volatile
и restrict
) к типу указателя, в который преобразуется массив, помещая их в скобки.
Анонимная функция не поддерживается стандартным языком программирования C, но поддерживается некоторыми диалектами C, такими как GCC [9] и Clang .
GNU Compiler Collection (GCC) поддерживает анонимные функции, смешанные с вложенными функциями и выражениями операторов. Он имеет форму:
( { return_type anonymous_functions_name ( параметры ) { function_body } anonymous_functions_name ; } )
Следующий пример работает только с GCC. Из-за того, как раскрываются макросы, l_body
не может содержать запятых вне скобок; GCC рассматривает запятую как разделитель между аргументами макроса. Аргумент l_ret_type
можно удалить, если __typeof__
он доступен; в примере ниже использование __typeof__
on array вернет testtype *
, который можно разыменовать для фактического значения, если необходимо.
#include <stdio.h> //* это определение анонимной функции */ #define lambda(l_ret_type, l_arguments, l_body) \ ({ \ l_ret_type l_anonymous_functions_name l_arguments \ l_body \ &l_anonymous_functions_name; \ })#define forEachInArray(fe_arrType, fe_arr, fe_fn_body) \ { \ int i=0; \ for(;i<sizeof(fe_arr)/sizeof(fe_arrType);i++) { fe_arr[i] = fe_fn_body(&fe_arr[i]); } \ }typedef struct { int a ; int b ; } testtype ; void printout ( const testtype * array ) { int i ; for ( i = 0 ; i < 3 ; ++ i ) printf ( " %d %d \n " , array [ i ]. a , array [ i ]. b ); printf ( " \n " ); } int main ( void ) { testtype array [] = { { 0 , 1 }, { 2 , 3 }, { 4 , 5 } }; printout ( array ); /* анонимная функция задана как функция для foreach */ forEachInArray ( testtype , array , lambda ( testtype , ( void * item ), { int temp = ( * ( testtype * ) item ). a ; ( * ( testtype * ) item ). a = ( * ( testtype * ) item ). b ; ( * ( testtype * ) item ). b = temp ; return ( * ( testtype * ) item ); })); printout ( array ); return 0 ; }
Clang поддерживает анонимные функции, называемые блоками , [10], которые имеют вид:
^ return_type ( параметры ) { тело_функции }
Тип блоков выше — return_type (^)(parameters)
.
Используя вышеупомянутое расширение блоков и Grand Central Dispatch (libdispatch), код может выглядеть проще:
#include <stdio.h> #include <dispatch/dispatch.h> int main ( void ) { void ( ^ count_loop )() = ^ { for ( int i = 0 ; i < 100 ; i ++ ) printf ( "%d \n " , i ); printf ( " ах ах \n " ); }; /* Передать как параметр другой функции */ dispatch_async ( dispatch_get_global_queue ( DISPATCH_QUEUE_PRIORITY_DEFAULT , 0 ), count_loop ); /* Вызвать напрямую */ count_loop (); вернуть 0 ; }
-fblocks
и скомпонован с помощью-lBlocksRuntime
Следующие слова зарезервированы и не могут использоваться в качестве идентификаторов:
|
|
|
|
Реализации могут резервировать другие ключевые слова, такие как asm
, хотя реализации обычно предоставляют нестандартные ключевые слова, которые начинаются с одного или двух подчеркиваний.
Идентификаторы C чувствительны к регистру (например, foo
, FOO
и Foo
являются именами разных объектов). Некоторые компоновщики могут сопоставлять внешние идентификаторы с одним регистром, хотя это нетипично для большинства современных компоновщиков.
Текст, начинающийся с токена /*
, рассматривается как комментарий и игнорируется. Комментарий заканчивается на следующем */
; он может встречаться внутри выражений и может занимать несколько строк. Случайное пропуск терминатора комментария проблематично, поскольку правильно построенный терминатор комментария следующего комментария будет использоваться для завершения исходного комментария, а весь код между комментариями будет считаться комментарием. Комментарии в стиле C не вкладывают друг в друга; то есть случайное размещение комментария внутри комментария приведет к непредвиденным результатам:
/*Эта строка будет проигнорирована./*Здесь может быть выдано предупреждение компилятора. Эти строки также будут проигнорированы.Открывающий комментарий токен выше не начал новый комментарий,а закрывающий комментарий токен ниже закроет комментарий, начатый в строке 1.*/Эта строка и строка ниже не будут проигнорированы . Обе , скорее всего , приведут к ошибкам компиляции . */
Комментарии в стиле C++//
начинаются с конца строки и продолжаются до нее. Этот стиль комментариев возник в BCPL и стал допустимым синтаксисом C в C99 ; он недоступен в оригинальном K&R C или в ANSI C :
// эта строка будет проигнорирована компилятором/* эти строки будут проигнорированы компилятором */x = * p /*q; /* этот комментарий начинается после 'p' */
Параметры , указанные в командной строке , передаются в программу на языке C с двумя предопределенными переменными — количеством аргументов командной строки argc
и отдельными аргументами в виде строк символов в массиве указателей argv
. Таким образом, команда:
мойФильтр p1 p2 p3
в результате получается что-то вроде:
м | у | Ф | я | л | т | \0 | п | 1 | \0 | п | 2 | \0 | п | 3 | \0 |
аргв[0] | аргв[1] | аргв[2] | аргв[3] |
Хотя отдельные строки представляют собой массивы смежных символов, нет гарантии, что строки хранятся как непрерывная группа.
Имя программы, argv[0]
, может быть полезным при печати диагностических сообщений или для того, чтобы сделать один двоичный файл многоцелевым. Отдельные значения параметров могут быть доступны с помощью argv[1]
, argv[2]
, и argv[3]
, как показано в следующей программе:
#include <stdio.h> int main ( int argc , char * argv []) { printf ( "argc \t = %d \n " , argc ); for ( int i = 0 ; i < argc ; i ++ ) printf ( "argv[%i] \t = %s \n " , i , argv [ i ]); }
В любом достаточно сложном выражении возникает выбор порядка вычисления частей выражения: может быть вычислено в порядке , , , , или в порядке , , , . Формально, соответствующий компилятор C может вычислять выражения в любом порядке между точками последовательности (это позволяет компилятору выполнять некоторую оптимизацию). Точки последовательности определяются следующим образом:(1+1)+(3+3)
(1+1)+(3+3)
(2)+(3+3)
(2)+(6)
(8)
(1+1)+(3+3)
(1+1)+(6)
(2)+(6)
(8)
&&
, которое можно прочитать и затем ) и логическое ИЛИ ( ||
, которое можно прочитать или иначе ).?:
): этот оператор сначала оценивает свое первое подвыражение, а затем второе или третье (но никогда оба) на основе значения первого.Выражения перед точкой следования всегда оцениваются раньше, чем после точки следования. В случае оценки с сокращенным вычислением второе выражение может не оцениваться в зависимости от результата первого выражения. Например, в выражении , если первый аргумент оценивается как ненулевой (истина), результат всего выражения не может быть ничем иным, кроме истины, поэтому не оценивается. Аналогично, в выражении , если первый аргумент оценивается как нуль (ложь), результат всего выражения не может быть ничем иным, кроме лжи, поэтому не оценивается.(a() || b())
b()
(a() && b())
b()
Аргументы вызова функции могут быть оценены в любом порядке, пока они все оценены к моменту входа в функцию. Например, следующее выражение имеет неопределенное поведение:
printf ( "%s %s \n " , argv [ i = 0 ], argv [ ++ i ]);
Аспект стандарта C (не уникальный для C) заключается в том, что поведение определенного кода считается «неопределенным». На практике это означает, что программа, созданная из этого кода, может делать все, что угодно, от работы так, как задумал программист, до сбоя при каждом запуске.
Например, следующий код создает неопределенное поведение, поскольку переменная b изменяется более одного раза без промежуточной точки следования:
#include <stdio.h> int main ( void ) { int b = 1 ; int a = b ++ + b ++ ; printf ( "%d \n " , a ); }
Поскольку между модификациями b в " b ++ + b ++" нет точки последовательности , возможно выполнение шагов оценки в более чем одном порядке, что приводит к неоднозначному утверждению. Это можно исправить, переписав код для вставки точки последовательности, чтобы обеспечить однозначное поведение, например:
а = б ++ ; а += б ++ ;
long long
был введен в стандарте C99 .