Приоритет выполнения операций




Операции в Си выполняются в соответствии следующей таблице приоритетов операций:

Лексемы Операция Класс Приоритет Ассоциативность
имена, литералы простые лексемы первичный   нет
a[k] индексы постфиксный   слева направо
f(…) вызов функции постфиксный   слева направо
. прямой выбор постфиксный   слева направо
-> опосредованный выбор постфиксный   слева направо
++ -- положительное и отрицательное приращение постфиксный   слева направо
++ -- положительное и отрицательное приращение префиксный   справа налево
(имя типа) {init} составной литерал (C99) постфиксный   справа налево
sizeof размер унарный   справа налево
~ побитовое НЕ унарный   справа налево
! логическое НЕ унарный   справа налево
- + изменение знака, плюс унарный   справа налево
& адрес унарный   справа налево
* опосредование (разыменование) унарный   справа налево
(имя типа) приведение типа унарный   справа налево
* / % мультипликативные операции бинарный   слева направо
+ - аддитивные операции бинарный   слева направо
<<>> сдвиг влево и вправо бинарный   слева направо
<><= >= отношения бинарный   слева направо
==!= равенство/неравенство бинарный   слева направо
& побитовое И бинарный   слева направо
^ побитовое исключающее ИЛИ бинарный   слева направо
| побитовое ИЛИ бинарный   слева направо
&& логическое И бинарный   слева направо
|| логическое ИЛИ бинарный   слева направо
?: условие тернарный   справа налево
= += -= *= /= %= <<= >>= &= ^= |= присваивание бинарный   справа налево
, последовательное вычисление бинарный   слева направо

Пустой оператор

Самая простая языковая конструкция — это пустое выражение, называемое пустым оператором;

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

Инструкции

Инструкция — это некое элементарное действие:

(выражение);

Действие этого оператора заключается в выполнении указанного в теле оператора выражения.

Несколько идущих подряд инструкций образуют последовательность инструкций.

Блок вычислений

Инструкции могут быть сгруппированы в специальные вычислительные блоки следующего вида:

{

(последовательность инструкций)

},

ограниченные при помощи двух разделителей:

левая фигурная скобка ({) обозначает начало вычислительного блока,

правая фигурная скобка (}) обозначает конец вычислительного блока.

Вычислительный блок называют ещё составным оператором.

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

Условные операторы

В языке существует два условных оператора, реализующих ветвление программы:

оператор if, содержащий проверку одного условия;

и оператор switch, содержащий проверку нескольких условий.

Самая простая форма оператора if

if((условие)) (оператор)

(следующий оператор)

Оператор if работает следующим образом:

если выполнено условие, указанное в скобках, то выполняется первый оператор, и затем выполняется оператор, указанный после оператора if.

если условие, указанное в скобках, не выполнено, то сразу выполняется оператор, указанный после оператора if.

В частности, следующий ниже код, в случае выполнения заданного условия, не будет выполнять никаких действий:

if((условие));

поскольку, фактически, выполняется пустой оператор. Более сложная форма оператора if содержит ключевое слово else:

if((условие)) (оператор)

else (альтернативный оператор)

(следующий оператор)

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

Операторы выполнения цикла

Цикл — это фрагмент программного кода, содержащий

условие выполнения цикла — условие, которое постоянно проверяется;

и тело цикла — простой или составной оператор, выполнение которого зависит от условия цикла.

В соответствии с этим, различают два вида циклов:

цикл с предусловием, где сначала проверяется условие выполнения цикла, и, если условие выполнено, то выполняется тело цикла;

цикл с постусловием, где сначала выполняется тело цикла, а уже потом проверяется условие выполнения цикла, и, если условие выполнено, то…;

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

В Си предусмотрен оператор цикла с предусловием:

while(условие) [тело цикла]

и оператор с постусловием do-while:

do [тело цикла] while(условие)

Также имеется оператор

for(блокинициализации;условие;оператор) [тело цикла],

который эквивалентен следующему блоку операторов:

[блок инициализации]

while(условие)

{

[тело цикла]

[оператор]

}

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

Условие цикла — это логическое выражение. Однако неявное приведение типов позволяет использовать в качестве условия цикла арифметическое выражение. Это позволяет организовать т. н. «бесконечный цикл»:

while(1);

или то же самое, но уже с применением оператора for:

for(;;);

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

Операторы безусловного перехода

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

Оператор

goto [метка],

где [метка] — это некоторый (числовой) идентификатор, передаёт управление тому оператору, который помечен в программе указанной меткой:

[метка]: [оператор]

Если указанная метка отсутствует в программе, или, если существует несколько операторов с одной и той же меткой, компилятор сообщает об ошибке.

Передача управления возможна только в пределах той функции, где используется оператор перехода, следовательно, при помощи оператора goto нельзя передать управление в другую функцию.

Другие операторы перехода связаны с циклами и позволяют прервать выполнения тела цикла:

оператор break немедленно прерывает выполнение тела цикла, и происходит передача управления на оператор, следующий непосредственно сразу за оператором цикла;

оператор continue прерывает выполнение тела цикла и передаёт управление в начало цикла, что инициирует проверку условия цикла.

Оператор continue может быть использован только внутри операторов do, while и for; оператор break также может использоваться внутри оператора switch.

Существует два особых случая применения операторов break и continue:

если оператор continue встретился в теле оператора for, то сначала происходит выполнение оператора, а уже затем происходит проверка условия цикла, таким образом оператор continue предназначен для немедленного перехода к следующей итерации выполнения цикла;

если имеется несколько вложенных циклов, то оператор break, в зависимости от реализации, либо полностью прерывает выполнение всех вложенных циклов, либо прерывает выполнение только того цикла, в котором расположен сам оператор break.

Оператор возврата из функции

В Си определён оператор return, который прерывает выполнение функции, где использован данный оператор. Если функция не должна возвращать значение, то используется вызов

return;

Если функция должна возвращать какое-либо значение, то использует вызов

return[значение];

Если после оператора возврата в теле функции имеются ещё какие-то операторы, то эти операторы никогда не будут выполняться, и в этом случае компилятор может выдать предупреждение.

Объявление функции

Объявление функции имеет следующий формат:

[описатель] [имя] ([список]);,

где

[описатель] — описатель типа возвращаемого функцией значения;

[имя] — имя функции (уникальный идентификатор функции);

[список] — список (формальных) параметров функции.

Признаком объявления функции является символ «;», таким образом, объявление функции — это инструкция.

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

void

При необходимости в описателе могут присутствовать дополнительные элементы:

модификатор extern указывает на то, что определение функции находится в другом модуле;

модификатор static задаёт статическую функцию;

модификаторы pascal или cdecl влияют на обработку формальных параметров и связаны с подключением внешних модулей.

Список параметров функции задаёт сигнатуру функции.

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

Определение функции

Определение функции имеет следующий формат:

[описатель] [имя] ([список]) [тело]

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

Тело функции имеет следующий вид:

{

[последовательность операторов]

return ([возвращаемое значение]);

}

Вызов функции

Вызов функции заключается в выполнении следующих действий:

сохранение точки вызова в стеке;

выделение памяти под переменные, соответствующие формальным параметрам функции;

инициализация переменных значениями переменных (фактических параметров функции), переданных в функцию при её вызове, а также инициализация тех переменных, для которых в объявлении функции указаны значения по умолчанию, но для которых при вызове не были указаны соответствующие им фактические параметры;

передача управления в тело функции.

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

Если в функцию передаётся переменная, то при вызове функции создаётся её копия (в стеке выделяется память и копируется значение). Например, передача структуры в функцию вызовет копирование всей структуры целиком. Если же передаётся указатель на структуру, то копируется только значение указателя. Передача в функцию массива также вызывает лишь копирование указателя на его первый элемент. При этом для явного обозначения того, что на вход функции принимается адрес начала массива, а не указатель на единичную переменную, вместо объявления указателя после названия переменной можно поставить квадратные скобки, например:

voidexample_func(intarray[]); // array — указатель на первый элемент массива типа int

Си допускает вложенные вызовы. Глубина вложенности вызовов имеет очевидное ограничение, связанное с размером выделяемого программе стека. Поэтому в реализациях Си устанавливается некое предельное значение для глубины вложенности.

Частный случай вложенного вызова — это вызов функции внутри тела вызываемой функции. Такой вызов называется рекурсивным, и применяется для организации единообразных вычислений. Учитывая естественное ограничение на вложенные вызовы, рекурсивную реализацию заменяют на реализацию при помощи циклов.

Возврат из функции

При возврате из функции освобождается память, выделенная под параметры функции и под переменные, объявленные внутри функции, и управление возвращается в точку вызова.

Примитивные типы

Целые числа

Размер целочисленных типов данных варьируется от не менее 8 до не менее 32 бит. Стандарт C99 увеличивает максимальный размер целого числа - не менее 64 бит. Целочисленные типы данных используются для хранения целых чисел (тип char также используется для хранения ASCII-символов). Все размеры диапазонов представленных ниже типов данных минимальны и на отдельно взятой платформе могут быть больше.

Стандарт не требует, чтобы два разных типа имели разный размер: sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(longlong). Таким образом, даже типы char и long могут иметь одинаковый размер, но такие платформы очень редки. Стандарт гарантирует, что тип char всегда равен 1 байту.

Минимальный диапазон значений целых типов по стандарту определяется с -(2N-1) по 2N-1, где N — разрядность типа. Реализация компиляторов может расширять этот диапазон по своему усмотрению. На практике чаще используется диапазон с -2N по 2N-1. Минимальное и максимальное значения каждого типа указывается в файле limits.h в виде макросов.

Отдельное внимание стоит уделить типу char. Формально это отдельный тип, но фактически char эквивалентен либо signedchar, либо unsignedchar, в зависимости от компилятора.

Для того, чтобы избежать путаницы между размерами типов стандарт C99 ввел новые типы данных, описанные в файле stdint.h. Среди них такие типы как: intN_t, int_leastN_t, int_fastN_t, где N = 8, 16, 32 или 64. Приставка least- обозначает минимальный тип, способный вместить N бит, приставка fast- обозначает тип размером не менее 16 бит, работа с которым наиболее быстрая на данной платформе. Типы без приставок обозначают типы с фиксированном размером, равным N бит.

Типы с приставками least- и fast- можно считать заменой типам int, short, long, с той лишь разницей, что первые дают программисту выбрать между скоростью и размером.

Тип данных Размер Минимальный диапазон значений Первое появление
signedchar минимум 8 бит от -127 (= -(28-1)) до 127 K&R C
unsignedchar минимум 8 бит от 0 до 255 (=28-1) K&R C
char минимум 8 бит от -127 до 127 или от 0 до 255 в зависимости от компилятора K&R C
shortint минимум 16 бит от -32,767 (= -(215-1)) до 32,767 K&R C
unsignedshortint минимум 16 бит от 0 до 65,535 (= 216-1) K&R C
int минимум 16 бит от -32,767 до 32,767 K&R C
unsignedint минимум 16 бит от 0 до 65,535 (= 216-1) K&R C
longint минимум 32 бита от -2,147,483,647 до 2,147,483,647 K&R C
unsignedlongint минимум 32 бита от 0 до 4,294,967,295 (= 232-1) K&R C
longlongint минимум 64 бита от -9,223,372,036,854,775,807 до 9,223,372,036,854,775,807 C99
unsignedlonglongint минимум 64 бита от 0 до 18,446,744,073,709,551,615 (= 264-1) C99
int8_t 8 бит от -127 до 127 C99
uint8_t 8 бит от 0 до 255 (=28-1) C99
int16_t 16 бит от -32,767 до 32,767 C99
uint16_t 16 бит от 0 до 65,535 (= 216-1) C99
int32_t 32 бита от -2,147,483,647 до 2,147,483,647 C99
uin32_t 32 бита от 0 до 4,294,967,295 (= 232-1) C99
int64_t 64 бита от -9,223,372,036,854,775,807 до 9,223,372,036,854,775,807 C99
uint64_t 64 бита от 0 до 18,446,744,073,709,551,615 (= 264-1) C99
Типы int_leastN_t, uint_leastN_t, int_fastN_t и uint_fastN_t (N = 8, 16, 32 или 64), введенные стандартом C99, размером и диапазоном совпадают с соответствующими типами char, short, int и long.

 

Хранение данных

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

В Си есть три разных способа выделения памяти (классы памяти) для объектов:

Статическое выделение памяти: пространство для объектов создаётся в сегменте данных программы в момент компиляции; время жизни таких объектов совпадает со временем жизни этого кода. Изменение таких объектов ведёт к так называемому в стандарте «неопределённому поведению» (англ. undefinedbehaviour). На практике эта операция приводит к ошибке во время выполнения.

Автоматическое выделение памяти: объекты можно хранить в стеке; эта память затем автоматически освобождается и может быть использована снова, после того, как программа выходит из блока, использующего его.

Динамическое выделение памяти: блоки памяти нужного размера могут запрашиваться во время выполнения программы с помощью библиотечных функций malloc, realloc, calloc из области памяти, называемой кучей. Эти блоки освобождаются и могут быть использованы снова после вызова для них функции free.

Простейшая программа на Си

Простейшая программа на Си имеет следующий вид:

main() {}

По умолчанию предполагается, что основная функция программы (функция main()) возвращает целое число, поэтому такая программа должна компилироваться (возможно, с выдачей одного или нескольких предупреждений), если компилятор реализует стандарт ANSI C. Если, однако, компилятор следует стандарту C99, то такой код не будет компилироваться, и потребуется явное описание типа возвращаемого функцией main() значения. Допускается не писать оператор return у функции main(). В таком случае, согласно стандарту, функция main возвращает 0 (включая исполнение всех обработчиков, назначенных на exit()), подразумевая, что программа успешно завершилась

Программа «Hello, World»

Эта простая программа приведена ещё в первом издании книги «Язык программирования Си» Кернигана и Ритчи:

#include <stdio.h> intmain(){printf("Hello, world!"); return 0;}

Эта программа печатает сообщение «Hello, world!» на стандартном устройстве вывода.

Программа, иллюстрирующая возможность функционального программирования на языке Си

Программа использует так называемые «функции высшего порядка» и «чистые функции»:

1 #include <stdio.h> 2 #include <stdlib.h> 3 4 intadd_one(const int)__attribute__((const)); 5 intsubtract_one(const int)__attribute__((const)); 6 intmodify(const int,int(*f)(const int));7 8 intadd_one(const intnum) return num+1; 9 intsubtract_one(const intnum) return num-1;10 intmodify(const intnum,int(*f)(const int)) return f(num);11 12 intmain()13 {14 intnum_1=modify(10,*(add_one));15 printf("%d \n ",num_1);16 17 num_1=modify(10,*(subtract_one));18 printf("%d \n ",num_1);19 20 return EXIT_SUCCESS;21 }22 23 // stdout: 24 // 11 25 // 9

В приведенном выше примере атрибут const указывает компилятору, что возвращаемое функцией значение зависит только от входных параметров и функция не использует глобальные переменные. Более «мягким» аналогом атрибутом const является атрибут pure, который позволяет функции использовать глобальные переменные.

 



Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2022-10-12 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: