stringtranslate.com

Синтаксис Си

Фрагмент кода на языке C , который выводит «Hello, World!»

Синтаксис языка программирования 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 представление могло быть альтернативным дополнением до единиц или знаком и величиной , но на практике это не было так в течение десятилетий в современных nardware). Во многих случаях существует несколько эквивалентных способов обозначения типа; например, и являются синонимами.signed short intshort

Представление некоторых типов может включать неиспользуемые биты "заполнения", которые занимают память, но не включены в ширину. В следующей таблице представлен полный список стандартных целочисленных типов и их минимально допустимая ширина (включая любой знаковый бит).

Тип charотличается от обоих signed charи unsigned char, но гарантированно имеет то же представление, что и один из них. Типы _Boolи long longстандартизированы с 1999 года и могут не поддерживаться старыми компиляторами C. _BoolДоступ к типу обычно осуществляется через typedefимя bool, определенное стандартным заголовком stdbool.h.

В общем, ширина и схема представления, реализованные для любой данной платформы, выбираются на основе архитектуры машины, с некоторым учетом простоты импорта исходного кода, разработанного для других платформ. Ширина типа intособенно сильно различается среди реализаций C; она часто соответствует наиболее «естественному» размеру слова для конкретной платформы. Стандартный заголовок limits.h определяет макросы для минимальных и максимальных представимых значений стандартных целочисленных типов, реализованных на любой конкретной платформе.

В дополнение к стандартным целочисленным типам могут быть и другие "расширенные" целочисленные типы, которые могут использоваться для typedefs в стандартных заголовках. Для более точной спецификации ширины программисты могут и должны использовать typedefs из стандартного заголовка stdint.h .

Целочисленные константы могут быть указаны в исходном коде несколькими способами. Числовые значения могут быть указаны как десятичные (пример: 1022), восьмеричные с нулем ( 0) в качестве префикса ( 01776), или шестнадцатеричные с 0x(нулем x) в качестве префикса ( 0x3FE). Символ в одинарных кавычках (пример: 'R'), называемый "символьной константой", представляет значение этого символа в наборе символов выполнения с типом int. За исключением символьных констант, тип целочисленной константы определяется шириной, необходимой для представления указанного значения, но всегда имеет ширину не менее int. Это можно переопределить, добавив явный модификатор длины и/или знака; например, 12luимеет тип unsigned long. Отрицательных целочисленных констант нет, но тот же эффект часто можно получить, используя унарный оператор отрицания " -".

Перечисляемый тип

Перечислимый тип в C, указанный с помощью enumключевого слова и часто называемый просто "enum" (обычно произносится как / ˈ n ʌ m / EE -num или / ˈ n 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 .

Константы с плавающей точкой могут быть записаны в десятичной системе счисления , например 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.

ПРИМЕЧАНИЕ C не определяет основание для float , double и long double . Реализация может выбрать представление float , double и long double таким же, как и у десятичных типов с плавающей точкой. [2]

Несмотря на это, основание системы счисления исторически было двоичным (основание 2), то есть числа вроде 1/2 или 1/4 являются точными, но не 1/10, 1/100 или 1/3. С десятичной плавающей точкой все те же числа являются точными плюс числа вроде 1/10 и 1/100, но все еще не например 1/3. Ни одна известная реализация не выбирает десятичное основание для ранее известных двоичных типов. Поскольку большинство компьютеров даже не имеют оборудования для десятичных типов, а те немногие, которые имеют (например, мэйнфреймы IBM начиная с IBM System z10 ), могут использовать явно десятичные типы.


Спецификаторы класса хранения

Каждый объект имеет класс хранения. Это определяет в основном длительность хранения, которая может быть статической (по умолчанию для глобального), автоматической (по умолчанию для локального) или динамической (выделенной), вместе с другими функциями (связь и подсказка регистра).

1 Выделяется и освобождается с помощью библиотечных функций malloc()и free().

Переменные, объявленные внутри блока по умолчанию, имеют автоматическое хранение, как и те, которые явно объявлены с помощью [примечание 2] или спецификаторов класса хранения. Спецификаторы и могут использоваться только внутри функций и объявлений аргументов функций; как таковой спецификатор всегда избыточен. Объекты, объявленные вне всех блоков, и те, которые явно объявлены с помощью спецификатора класса хранения, имеют статическую продолжительность хранения. Статические переменные по умолчанию инициализируются компилятором нулем . autoregisterautoregisterautostatic

Объекты с автоматическим хранилищем являются локальными по отношению к блоку, в котором они были объявлены, и отбрасываются при выходе из блока. Кроме того, объекты, объявленные с 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 и т. д. могут быть указаны случайно, с неопределенными результатами.

Поскольку массивы и указатели взаимозаменяемы, адреса каждого из элементов массива могут быть выражены в эквивалентной арифметике указателей . Следующая таблица иллюстрирует оба метода для существующего массива:

Поскольку выражение 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], чтобы получить доступ к четвертому целому числу.

Аналогичным образом можно объявить многомерные массивы.

Многомерный массив не следует путать с массивом указателей на массивы (также известным как вектор Илиффе или иногда массив массивов ). Первый всегда прямоугольный (все подмассивы должны быть одинакового размера) и занимает непрерывную область памяти. Последний представляет собой одномерный массив указателей, каждый из которых может указывать на первый элемент подмассива в другом месте памяти, и подмассивы не обязательно должны быть одинакового размера. Последний может быть создан путем многократного использования 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\"."указывают (компилятору), что внутренняя пара кавычек предназначена как фактическая часть строки, а не как по умолчанию разделитель (конечная точка) самой строки.

Обратные косые черты можно использовать для ввода в строку различных управляющих символов и т. д.:

Использование других экранированных символов обратной косой черты не определено стандартом 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спецификатор, чтобы исключить необходимость в ключевом слове structor 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, реализация определяет, является ли битовое поле, объявленное как type int, без указания signedor unsigned, знаковым или беззнаковым. Таким образом, рекомендуется явно указывать signedor 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 могут быть вложенными; метка caseor 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 .

Разнообразный

Зарезервированные ключевые слова

Следующие слова зарезервированы и не могут использоваться в качестве идентификаторов:

Реализации могут резервировать другие ключевые слова, такие как 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

в результате получается что-то вроде:

Хотя отдельные строки представляют собой массивы смежных символов, нет гарантии, что строки хранятся как непрерывная группа.

Имя программы, 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 ++" нет точки последовательности , возможно выполнение шагов оценки в более чем одном порядке, что приводит к неоднозначному утверждению. Это можно исправить, переписав код для вставки точки последовательности, чтобы обеспечить однозначное поведение, например:

а = б ++ ; а += б ++ ;    

Смотрите также

Примечания

  1. ^ ab Модификатор long longбыл введен в стандарте C99 .
  2. ^ Значение auto — это спецификатор типа, а не спецификатор класса хранения в C++0x.
  3. ^ см. UTF-8 первый раздел для ссылок
  4. ^ Другие типы, определяемые реализацией, также разрешены. C++ позволяет использовать все целочисленные и перечислимые типы, и многие компиляторы C делают то же самое.

Ссылки

  1. ^ "WG14-N2412: Представление знака в формате дополнения до двух" (PDF) . open-std.org . 11 августа 2019 г. Архивировано (PDF) из оригинала 27 декабря 2022 г.
  2. ^ "WG14-N2341: ISO/IEC TS 18661-2 - Расширения с плавающей точкой для C - Часть 2: Десятичная арифметика с плавающей точкой" (PDF) . open-std.org . 26 февраля 2019 г. Архивировано (PDF) из оригинала 21 ноября 2022 г.
  3. ^ abc Клеменс, Бен (2012). 21st Century C. O'Reilly Media . ISBN 978-1449327149.
  4. ^ Балагурусами, Э. Программирование на ANSI C. Tata McGraw Hill. стр. 366.
  5. ^ "Препроцессор C: поведение, определяемое реализацией". gcc.gnu.org .
  6. ^ "Строковые и символьные литералы (C++)". Документация Visual C++ 19. Получено 20 ноября 2019 г.
  7. ^ Керниган и Ричи
  8. ^ Тэтхэм, Саймон (2000). "Сопрограммы в C" . Получено 30 апреля 2017 г.
  9. ^ "Statement Exprs (использование коллекции компиляторов GNU (GCC))". gcc.gnu.org . Получено 2022-01-12 .
Общий

Внешние ссылки