The OpenNET Project / Index page

[ новости /+++ | форум | wiki | теги | ]

Каталог документации / Раздел "Программирование, языки" / Оглавление документа
Вперед Назад Содержание

5. Выражения и Присвоения

5.1 Введение

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

"Операнд" это постоянное или переменное значение, которым манипулирует выражение. Каждый операнд в выражении это тоже, в свою очередь, выражение, т.к. он представляет собой отдельное значение. При вычислении значения результирующая величина зависит от порядка следования операторов в выражении, "последовательностей точек" и "побочных эффектов", если они есть. Порядок следования операторов определяет их группирование при вычислении значения выражения. Побочные эффекты это изменения, которые вызваны вычислением выражения. В выражении с побочными эффектами вычисление одного операнда может повлиять на значение другого. Для некоторых операторов порядок вычисления операндов также сказывается на значении всего выражения. Раздел 5.2 содержит описания форматов и правил проведения вычислений операндов языка С, включая случаи рассмотрения побочных эффектов и последовательностей точек.

"Операторы" задают способ манипулирования операндом или операндами в выражении. Операторы С рассмотрены в Разделе 5.3 .

В языке С присвоения также считаются выражениями, т.к. результатом присвоения будет значение. Значением присвоения будет величина, которую он присваивает. Кроме простого оператора присвоения (=) в языке С имеются сложные операторы присвоения, которые и трансформируют и присваивают их операнды. Операторы присвоения описаны в Разделе 5.4 .

Значения, которые представлены каждым операндом в выражении имеют тип, который может быть преобразован в другой тип в определенной ситуации. Преобразования типов происходят в присвоениях, приведении типов, вызовах функций и операциях. (В Разделе 5.5 заданы правила приритета выполнения операций в С; побочные эффекты рассмотрены в Разделе 5.2.11 и преобразование типов в Разделе 5.6.)

5.2 Операнды С

Операнды в С это константы, идентификаторы, строки, вызовы функций, выражения индексов, выражения выбора компонент или более сложные выражения, которые формируются комбинированием операндов с операторами или заключением операндов в скобки. Все операнды, которые представляют собой постоянное значение, называются "постоянными выражениями".

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

Константы

Постоянный операнд имеет то значение и тип, которое совпадает со значением, которое он представляет. Символьная константа имеет тип int. Целая константа имеет тип int, long, unsigned int или unsigned long в зависимости от размера величины и способа ее задания. Константы с плавающей точкой всегда имеют тип double. Строковые литералы считаются массивами символов и рассматриваются в Разделе 5.2.3.

Идентификаторы

"Идентификатор" задает имя переменной или функции. Каждый идентификатор имеет тип, который устанавливается при его объявлении. Значение идентификатора зависит от его типа следующим образом:

Строки

      Синтаксис:     "строка"["строка"]   
"Строковый литерал" это символ или последовательность символов, заключенная в двойные цитатные скобки. Два или несколько строковых литералов, которые разделены единственным пробелом, сливаются в один строковый литерал. Строковый литерал хранится как массив элементов типа char и инициализируется заключенной в цитатные скобки последовательностью символов. Строковый литерал представляет указатель, значение которого есть адрес первого элемента массива. Адрес первого элемента строки есть константа, поэтому значение, представляемое строковым выражением также есть константа.

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

Последний символ строки это всегда пустой символ. Хотя пустой символ и не виден в строковом выражении, он автоматически добавляется как последний элемент при записи строки. Например, строка "abc" в действительности состоит из четырех элементов, а не из трех.

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

      Синтаксис:     выражение([список-выражений])   
"Вызов функции" состоит из выражения, за которым стоит необязательный элемент - список выражений в скобках, где Выражение вызова функции имеет значение и тип возвращаемого функцией значения. Если функция возвращает тип void (т.е. было объявлено, что она не будет возвращать значений), то выражение вызова функции тоже будет иметь тип void. Если вызванная функция возвращает управление без выполнения оператора return, то значение выражения вызова функции не определено. (Дополнительную информацию о вызовах функций можно найти в Главе "Функции".)

Выражения индексов

      Синтаксис:     выражение1 [выражение2]   
Выражения индексов представляют значение адреса, который на "выражение2" позиций стоит после "выражения1". Обычно значение представленное выражением1 есть значение указателя, например, идентификатор массива, а "выражение2" есть интегральное значение. Однако, все что требуется с точки зрения синтаксиса, это чтобы одно из выражений было типа указателя, а другое - интегрального типа. Т.о. интегральная величина может стоять в позиции выражения1, а значение указателя может стоять в скобках на месте выражения2, или "индекса". Независимо от поррядка следования значений, выражение2 должно быть заключено в скобки ([]).

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

Ссылки на одномерный массив

Выражение индекса вычисляется добавлением интегрального значения к значению указателя, а затем применением к результату оператора адресации (*). (Оператор адресации рассматривается в Разделе 5.3.6.) Т.о. для одномерного массива следующие четыре выражения эквивалентны (предполагается, что a - указатель, а b ­ целое):

                a[b]   
                *(a+b)   
                *(b+a)   
                b[a]   
В соответствии с правилами преобразования для оператора сложения (приведенными в Разделе 5.3.6) интегральное значение преобразуется в адрес сдвига умножением его на длину адресуемого указателем типа.

Например, предположим, что идентификатор line ссылается на массив значений int. Для вычисления выражения индекса line[i] используется следующая процедура:

  1. Целое значение i умножается на число байт, которое определяется длиной элемента int. Преобразованное значение i теперь соответствует i-ой позиции int.
  2. Это преобразованное значение добавляется к начальному значению указателя (line) для получения адреса, который отстоит на i int позиций от line.
  3. К новому адресу применяется оператор адресации. результатом будет значение элемента массива в этой позиции (line[i]).

Примечание

Значению первого элемента line соответствует следующее значение выражения индекса:

                line[0]   
Аналогично, для указания пятой позиции от line или шестого элемента массива используется следующая ссылка:

                line[5]   

Ссылки на многомерный массив

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

           выражение1[выражение2][выражение3]...   
Выражение индекса ассоциируется слева направо. Самое левое выражение, выражение1[выражение2], вычисляется первым. Адрес является результатом сложения выражения1 и выражения2 и формирует указатель. Затем к этому указателю добавляется выражение3 и формируется новый указатель и т.д. до последнего выражения индекса. Оператор адресации (*) применяется после вычисления последнего выражения индекса, если величина адреса финального указателя не имеет тип массива (см. Пример 3).

Выражения с несколькими индексами указывают на элементы "многомерных массивов". Многомерный массив это массив, элементы которого есть массивы. Например, первый элемент трехмерного массива это двумерный массив.

Для следующих примеров объявляется массив с именем prop с тремя элементами, каждый из которых это массив 4 на 6 значений int.

                int prop[3][4][6];   
                int i, *ip, (*ipp)[6];   

Пример 1

В данном примере показано, как указать второй отдельный int элемент prop. Массивы всегда храняться по строкам, поэтому наиболее быстро меняется последний индекс. Выражение prop[0][0][2] указывает следующий (третий) элемент массива, поэтому:

                i = prop[0][0][1];   

Пример 2

Пример показывает более сложную ссылку на отдельный элемент prop. Выражение вычисляется следующим образом:

  1. Первый индекс, 2, умножается на размер массива 4 на 6 int и добавляется к значению указателя prop. Результат указывает на третий массив 4 на 6 в prop.
  2. Второй индекс, 1, умножается на размер 6-элементного массива int и добавляется к адресу, представленному prop[2].
  3. Каждый элемент 6-элементного массива есть значение int. Следовательно, финальный индекс, 3, умножается на размер int до его добавления к prop[2][1]. Результирующий адрес указывает на четвертый элемент 6-элементного массива.
  4. К значению указателя применяется оператор адресации. Результатом является элемент int, расположенный по этому адресу.
                    i = prop[2][1][3];   
    

Пример 3

Примеры 3 и 4 рассматривают случаи, когда оператор адресации не применяется.

В Примере 3 выражение prop[2][1] является допустимой ссылкой на трехмерный массив prop. Она указывает на 6-элементный массив (объявленный выше в Примере 1). Т.к. значение указателя указывает на массив, оператор адресации не применяется.

                ip = prop[2][1];   

Пример 4

Как и для случая в Примере 3, результат выражения prop[2] есть указатель, указывающий на двумерный массив.

                ipp = prop[2];   

Выражения выбора компоненты

      Синтаксис:     выражение.идентификатор   
                     выражение->идентификатор   
"Выражение выбора компоненты" указывает компоненту структуры и об'единения. Такие выражения имеют значение и тип выбранный компоненты. Как это показано в строке синтаксиса, выражение выбора компоненты может иметь одну из двух следующих форм:
  1. При первой форме, выражение.идентификатор, выражение соответствует значению типа struct или union, а идентификатор задает имя компоненты указанной структуры или об'единения.
  2. При второй форме, выражение->идентификатор, выражение соответствует указателю на структуру или об'единение, а идентификатор задает имя компоненты указанной структуры или об'единения.
Эти две формы задания выражения выбора компоненты имеют одинаковое действие. Действительно, выражение, которое использует оператор выборки указателя (->) является просто краткой формой записи выражения, в котором используется точка (.), если выражение до точки состоит из оператора адресации (*), прмененному к значению указателя. (Оператор адресации рассматривается в Разделе 5.3.3.) Таким образом,

                выражение->идентификатор   
эквивалентно

                (*выражение).идентификатор   
где "выражение" есть значение указателя.

В Примерах 1, 2 и 3 делается ссылка на следующее объявление структуры:

                struct pair {   
                     int a;   
                     int b;   
                     struct pair *sp;   
                     } item, list[10];   

Пример 1

В данном примере адрес структуры item присваивается компоненте sp структуры. Это значит, что item содержит указатель на себя.

                item.sp = &item;   

Пример 2

В данном примере указатель item.sp используется с оператором выбора указателя (->) для присвоения значения компоненте a.

                (item.sp)->a = 24;   

Пример 3

В данном примере показано, как выбрать отдельную компоненту структуры из массива структур.

                list[8].b = 12;   

Выражения с операторами

Выражения с операторами могут быть "унарными", "бинарными" и "тернарными" выражениями. Унарное выражение состоит либо из унарного оператора, который применяется к операнду, либо из ключевого слова sizeof, за которым следует выражение. Это выражение может быть либо именем переменной либо выражением преобразования типа. При этом выражение преобразования типа должно быть заключено в скобки.

                унарный-оператор операнд   
                sizeof выражение   
Бинарные выражения состоят из двух операндов, объединенных бинарным оператором:

                операнд бинарный-оператор операнд   
Тернарные выражения состоят из трех операндов, объединенных тернарным оператором (?:) :

                операнд ? операнд : операнд   
Выражения с операторами включают в себя также и выражения присвоения, которые используют унарные или бинарные операторы присвоения. Унарными операторами присвоения являются операторы увеличения (++) и уменьшения (--). Бинарными операторами присвоения являются оператор простого присвоения (=) и составные операторы присвоения. Каждый составной оператор присвоения является комбинацией других бинарных операторов с оператором простого присвоения. Выражения присвоения имеют следующий вид:

                операнд++   
                операнд-­   
                ++операнд   
                --операнд   
                операнд=операнд   
                операнд составной-оператор-присвоения операнд   
Операторы присвоения детально описаны в Разделах 5.4.1 - 5.4.4.

Выражения в скобках

Можно заключать любой операнд в скобки без изменения типа или значения выражения. Например, в соледующем выражении скобки вокруг 10+5 означают, что величина 10+5 является левым операндом для оператора деления (/):

                (10 + 5) / 5   
Результатом (10+5)/5 будет 3. Без скобок, 10+5/5 даст своим результатом 11.

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

Выражения преобразования типа

Преобразование типа дает метод явного пребразования типа объекта в конкретной ситуации. Выражение преобразования типа имеет следующий вид:

                (имя-типа)операнд   
Преобразование типа может быть применено к объектам любого скалярного типа для их преобразования в любой другой скалярный тип. К явному преобразованию типа применимы те же самые правила, что определяют эффект неявного преобразования типа, рассмотренного в разделе "Преобразования при присвоении". Дополнительные ограничения могут явиться результатом действительных размеров или представлением конкретных типов в конкретной реализации. Представление типов рассмотрено в Главе "Объявления". Информацию о действительных размерах интегральных типов и указателей можно найти в вашем Руководстве по компилятору.

Любой объект может быть преобразован в тип void. Однако, если имя типа в выражении преобразования типа не void, операнд не может быть выражением void. Например, если функция имеет возращаемый тип void, его нельзя преобразовать в отличный от void тип. Обратите внимание на то, что выражение void* имеет тип указателя на void, а не тип void. Если объект преобразован в тип void, результирующее выражение не может быть присвоено никакому элементу. Аналогично, если объект преобразования типа имеет неприемлемое значение, к преобразованному объекту не может быть применено присвоение. Имена типов рассмотрены в Разделе 4.9. Значения рассматриваются в Разделе 5.4.1. Преоразование типа рассматривается в Разделе 5.6.

Постоянные выражения

Постоянное выражение это выражение, значение которого не меняется. Операндами постоянного выражения могут быть целые константы, символьные константы, константы с плавающей точкой, перечислимые константы, преобразования типа, выражения sizeof и другие постоянные выражения. Можно использовать операторы для комбинирования и модификации операндов, как это рассмотрено в Разделе 5.2.7, со следующими ограничениями:

  1. В постоянном выражении нельзя использовать оператор присвоения (см. Раздел 5.4) или бинарный оператор последовательного вычисления (,).
  2. Можно использовать унарный оператор получения адреса (&) только при определенных инициализациях (как это описано в последнем параграфе данного раздела).
На использованные в директивах предпроцессора постоянные выражения налагаются дополнительные ограничения. Соответственно, такие выражения называются "ограниченными постоянными выражениями". Ограниченные постоянные выражения не могут содержать выражений sizeof, перечислимых констант, преобразований типа в другой тип и констант с плавающей точкой. Однако, они могут содержать специальные постоянные выражения defined(идентификатор). (Дополнительная информация о таких выражениях содержится в Разделе 8.2.2, "Директивы #define".)

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

Побочные эффекты

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

Порядок вычисления значения выражения зависит от конкретной реализации, за исключением гарантируемого языком программирования конкретного приритета выполнения операций (как это рассматривается в Разделе 5.5).

Например, в следующем вызове функции возникает побочный эффект:

                     add(i+1,i=j+2)   
Аргументы в вызове функции могут быть вычислены в произвольном порядке. Выражение i+1 может быть вычеслено как до выражения i=j+2, так и после него. Результат при этом будет разный.

Унарные операции увеличения и уменьшения значения используют присвоение, такие операции могут вызвать побочные эффекты, как это показано в следующем примере:

                d = 0;   
                a = b++ = c++ = d++;   
В этом примере значение a непредсказуемо. Значение d (первоначально 0) может быть присвоено с, затем b, а затем а до увеличения значения любой из переменных. В данном случае а будет равно 0.

Второй способ вычисления выражения начинается с вычисления операнда c++ = d++. Значение d (первонначально 0) присваивается с, а затем значения c и d увеличиваются. Далее, значение с (теперь 1) присваивается b, и значение b затем увеличивается. Наконец, увеличенное значение b присваивается а.

Язык С не задает порядок проведения вычислений при побочном эффекте, поэтому могут быть реализованы оба из описанных выше методов вычисления. Для обеспечения мобильности и ясности Ваших программ следует избегать операторов, которые зависят от конкретной последовательности проведения вычислений и могут вызвать побочные эффекты.

Точки упорядочевания

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

Некоторые операторы могут служить такими точками, включая следующие:

Другими подобными точками являются:

5.3 Операторы С

Операторы языка С обрабатывают один операнд (унарные операторы), два операнда (бинарные операторы) или три операнда (тернарный оператор). Операторы присвоения включают и унарные и бинарные операторы. Операторы присвоения рассматриваются в Разделе 5.4.

Унарные операторы ставятся до их операндов и ассоциируются справа налево. В языке С имеются следующие унарные операторы:

      - ~ !     отрицание и дополнение   
      * &       адресация и получение адреса   
      sizeof    размер   
      +         унарный плюс   

Бинарные операторы ассоциируются слева направо. В языке С имеются следующие бинарные операторы:

      * / %               мультипликативные   
      + -                 аддитивные   
      << >>               сдвиг   
      < > <= >= == !=     отношения   
      & | ^               битовые   
      && ||               логические   
      ,                   последовательные вычисления   
В языке С есть только один тернарный оператор условия (? :). Он ассоциируется справа налево.

Обычные арифметические преобразования

Большинство операторов С выполняют преобразование типа, чтобы привести операнды выражения к некоторому общему типу или для расширения коротких значений до размеров целого, который используется в машинных операциях. Выполняемые операторами С преобразования завмсят от конкретных операторов и типов операнда или операндов. Однако, многие операторы выполняют аналогичные преобразования над операндами целого типа и типа с плавающей точкой. Эти преобразования известны, как "арифметические преобразования", т.к. они применяются к типам значений, обычно используемым в арифметике.

Арифметические преобразования, собранные в данном разделе, называются "обычными арифмитическими преобразованиями. При рассмотрении каждого оператора в последующих разделах оговаривается, выполняет ли данный оператор обычные арифмитические преобразования. Кроме того, указаны выполняемые оператором дополнительные арифметические преобразования, если они имеют место. Не задан какой-либо порядок следования. Он определяется следующим алгоритмом, который применяется к каждой бинарной операции в выражении:

  1. Все опернды типа float преобразуются в тип double.
  2. Если один операнд имеет тип long double, то и другой операнд преобразуется в тип long double.
  3. Если один операнд имеет тип double, то и другой операнд преобразуется в тип double.
  4. Все операнды типа char или short преобразуются в тип int.
  5. Все операнды типа unsigned char или unsigned short преобразуются в тип unsigned int.
  6. Если один операнд имеет тип unsigned long, то и другой операнд преобразуется в тип unsigned long.
  7. Если один операнд имеет тип long, то и другой операнд преобразуется в тип long.
  8. Если один операнд имеет тип unsigned int, то и другой операнд преобразуется в тип unsigned int.
Следующий пример демонстрирует применение приведенного алгоритма:

                     long l;   
                     unsigned char uc;   
                     int i;   
                     f(l+uc*i);   
v Преобразование в данном примере будет проделано следующим образом:
  1. uc преобразуется в тип unsigned int (шаг 5).
  2. i преобразуется в тип unsigned int (шаг 8). Выполняется умножение и результатом будет тип unsigned int.
  3. uc*i преобразуется в тип long (шаг 7).
Выполняется сложение и результатом будет тип long.

Дополнение и унарный плюс

Операторы дополнения рассматриваются в следующем списке:

   
      -    Оператор арифметического отрицания дает своим   
           результатом отрицательное значение (бинарное дополнение)   
           своего операнда. Операнд должен быть целым значением   
           или значением с плавающей точкой. Этот оператор   
           выполняет простые арифметические преобразования.   
   
      ~    Оператор побитового дополнения дает своим результатом   
           побитовое дополнение для своего операнда. Операнд   
           должен иметь целый тип. Данный операнд выполняет   
           простые арифметические преобразования. Результат имеет   
           тип после преобразования.   
   
      !    Оператор логического отрицания дает значение 0, если   
           его операнд "истина" (не ноль), и значение 1, если его   
           операнд "ложь" (0). Результат имеет тип int. Операнд   
           может быть целым, значением с плавающей точкой или   
           поцнтером.   
   
      +    Оператор унарного плюса, предшествующий выражению в   
           скобках, форсирует группирование вложенных в скобки   
           операторов. Он используется с выражениями, состоящими   
           из нескольких ассоциативных или коммуникативных   
           бинарных операторов.   

Примечание

Оператор унарного плюса в Microsoft C реализован синтаксически, но не семантически для любого связанного с ним типа.

Пример 1

В данном примере новое значение x есть отрицание 987, или -987.

                     short x = 987;   
                           x = -x;   
Пример 2

В данном примере y присваивается новое значение, которое является к величине без знака 0xaaaa, или 0x5555.

                     unsigned short y = 0xaaaa;   
                                    y = ~y;   

Пример 3

В данном примере если x больше или равен y, то результат выражения есть 1 ("истина"), а если x меньше y, то результат есть 0 ("ложь").

                     if ( !(x<y) );   

Операторы адресации и получения адреса

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

Нельзы применять оператор получения адреса к компоненте битового поля структуры (см. Раздел 4.4.3, "Объявления структур") или к идентификатору, который объявлен со спецификатором класса хранения register (см. Радел 4.6).

В Примерах от 1 до 4 используются следующие объявления:

                     int *pa, x;   
                     int a[20];   
                     double d;   
Пример 1

В данном примере оператор получения адреса (&) берет адрес шестого элемента массива a. Результат записывается в указатель pa.

                     pa = &a[5];   
Пример 2

В данном примере оператор адресации (*) используется для получения доступа к значению int, которое хранится по адресу pa. Это значение присваивается целой переменной x.

                     x = *pa;   
Пример 3

В данном примере будет напечатано слово True. Данный пример демонстрирует, что результат применения операции адресации к адресу x совпадает с x.

                     if (x == *&x)   
                          printf("True\n");   

Пример 4

В данном примере показано надлежащее применение правил, показанных в Примере 3. Сначала адрес x приведением типа преобразуется в указатель типа double, а затем применение оператора адресации дает результат типа double.

                     d = *(double *)(&x);   
Пример 5

В данном примере объявляется функция roundup, и объявляются и инициализируются два указателя на roundup. Первый указатель proundup инициализируется с использованием только имени функции, а второй, pround, использует при инициализации оператор получения адреса. Результаты инициализации совпадают.

                     int roundup();   
                     int (*proundup) = roundup;   
                     int (*pround) = &roundup;   

sizeof

Оператор sizeof дает размер памяти в байтах, который связан с заданным идентификатором или типом. Этот оператор позволяет избежать задания в программе размеров данных, которые зависели бы от ЭВМ.

Выражение sizeof имеет вид:

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

При применении оператора sizeof к идентификатору массива результатом будет размер массива, а не размер указателя, который соответствует идентификатору массива.

При применении оператора sizeof к имени типа структуры или об'единения или к идентификатору типа структуры или об'единения, резу­ татом будет размер структуры или об'единения. Этот размер может включать в себя внутреннее или хвостовое выравнивание границ по памяти компонент структуры или об'единения. Т.о. результат может не соответствовать размеру, который вычисляется сложением выделяемых для каждой из компонент обрастей памяти.

Пример 1

Данный пример использует оператор sizeof для передачи размера int, который у разных ЭВМ разный, в качестве аргумента в функцию calloc. buffer хранит возвращаемое функцией значение.

                     buffer = calloc(100, sizeof (int) );   
Пример 2

В данном примере string это массив указателей на char. Число указателей это число элементов массива, но оно не задано. Используя оператор sizeof легко определить число указателей для определения числа элементов массива. Этим числом инициализуется const целое значение string_no. string_no нельзя модифицировать, т.к. это значение const.

      static char *string[] ={   
           "this is string one",   
           "this is string two",   
           "this is string three",   
           };   
      const int string_no = (sizeof strings) /(sizeof strings[0]);   

Мультипликативные операторы

Мультипликативные операторы выполняют операции умножения (*), деления (/), и взятие остатка (%). Операнды оператора взятия остатка должны быть интегрального типа. Операторы умножения и деления могут обрабатывать целые операнды и операнды с плавающей точкой, при этом типы операндов могут быть разные.

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

Примечание

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

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

      *    Оператор умножение вызывает умножение значений двух   
           своих операндов.   
   
      /    Оператор деления вызывает деление значения первого   
           операнда на второй. Если результат деления двух целых   
           операндов не есть целое число, то он округляется по   
           следующим правилам:   
   
                - Если оба операнда положительны или не имеют   
                  знака, то результат будет округлен до значения   
                  0.   
   
                - Если один из операндов отрицателен, то   
                  направление округления результата (к нулю или от   
                  него) зависит от реализации. Дополнительную   
                  информацию можно найти в Вашем Руководстве по   
                  компилятору.   
   
               Результат деления на ноль не определен.   
    
      %    Результатом оператора взятия остатка будет остаток от   
           деления одного операнда на другой. Если один или оба   
           операнда положительны или не имеют знака, то результат   
           будет положительным. Если хотя бы один из операндов   
           отрицательный, то знак результата зависит от   
           реализации. (Дополнительную информацию можно найти в   
           Вашем Руководстве по компилятору.) Если правый операнд   
           ноль, то результат не определен.   

Данные объявления были использованы во всех последующих примерах данного раздела:

                     int i = 10, j = 3, n;   
                     double x = 2.0, y;   
Пример 1

В данном примере x умножается на i и дает в результате 20.0. Результат имеет тип double.

                     y = x * i;   
Пример 2

В данном примере 10 делится на 3. Результат округляется до 0, давая целое значение 3.

                     n = i / j;   
Пример 3

В данном примере n присваивается целый остаток от деления 10 на 3.

                     n = i % j;   

Аддитивнные операторы

Аддитивные операторы выполняют сложение (+) и вычитание (-). Операндами могут быть целые значения или значения с плавающей точкой. Некоторые аддитивные операции могут быть выполнены и над значениями указателей, как это подчеркивается при рассмотрении каждого оператора.

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

Сложение (+)

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

Когда к указателю добавляется целая величина (i) она преобразуется путем ее умножения на размер величины, на которую указывает указатель. После преобразования это целое значение представляет собой i-ую позицию памяти, а каждая позиция имеет длину, задаваемую типом указателя. При добавлении целого значения к значению указателя результатом будет новый указатель, значение котрого соответствует адресу i-ой позиции от первоначального адреса. Значение нового указателя есть адрес величины того же самого типа, на который указывал первоначальный указатель.

Вычитание (-)

Оператор вычитания (-) отнимает значение второго операнда из первого. С этим оператором могут быть использованы следующие комбинации операндов:

При вычитании двух указателей, их разность преобразуется в целую велину со знаком, делением разности размеров значений, на которые они указывают. Результат будет соответствовать числу позиций памяти заданного типа между двумя адресами. Результат имеет смысл только для двух элементов одного и того же массива, как это будет рассмотрено позднее в данном разделе в "Арифметике указателей".

Когда из указателя вычитается целая величина (i) она преобразуется путем ее умножения на размер величины, на которую указывает указатель. После преобразования это целое значение представляет собой i-ую позицию памяти, а каждая позиция имеет длину, задаваемую типом указателя. При вычитании целого значения из значения указателя результатом будет новый указатель, значение котрого соответствует адресу i-ой позиции до первоначального адреса. Значение нового указателя есть адрес величины того же самого типа, на который указывал первоначальный указатель.

Арифметика указателей

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

Это предположение справедливо для компонент массива. По своему определению массив это совокупность значений одного типа. Его компоненты располагаются неприрывно в заданной области памяти. Однако, нет гарантии, что элементы любого отличного от массива типа располагаются в памяти непрерывно. Т.е. между позициями памяти могут появляться пропуски, даже если каздая из позиций имеет одинаковый тип. Т.о. результаты добавления или уменьшения адресов любых величин, кроме элементов массива, не определены.

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

Для ЭВМ сегментированной архитектуры (например, 8086/8088), аддитивные операции с указателем и целой величиной в некоторых случаях могут быть некорректными. Например, операция может дать в результате адрес, который находится вне границ массива. Дополнительная информация о моделях памяти содержится в Вашем Руководстве по компилятору.

Данные объявления используются в двух последующих примерах:

                     int = 4, j;   
                     float x[10];   
                     float *px;   
Пример 1

В данном примере значение i умножается на длину типа float и добавляется к &x[4]. В результате получаем указатель, который указывает на величину x[8].

                px = &x[4] + i; /* эквивалентно  px = &x[4+i]; */   

Пример 2

В данном примере адрес третьего элемента x (задаваемый x[i-2]) вычитается из адреса пятого элемента x (задаваемого x[i]). Разность делится на длину float и результатом будет целое значение 2.

                     j = &x[i] - &x[i-2];   

Сдвиг

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

Сдвиг влево обращает правые биты в 0. При сдвиге вправо свободные левые биты заполняются в соответствии с типом первого операнда после преобразования. Если этот тип unsigned, то они устанавливаются в 0. В противном случае они заполняются копиями бита знака.

Если второй операнд есть отрицательная величина, то результат операции сдвига не определен.

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

Пример

                     unsigned int x, y, z;   
                     x = 0x00aa;   
                     y = 0x5500;   
                     z = (x << 8) + (y >> 8);   
В данном примере x сдвигается влево на восемь позиций, а y сдвигается вправо на восемь позиций. Сдвинутые значения складываются и результат, 0xaa55, присваивается z.

Отношения

Бинарные операторы отношений сравнивают первый операнд со вторым и проверяют истинность заданного соотношения. Результат проверяемого выражения отношения равен 1, если оно "истина", и равен 0, если оно "ложь". Результат имеет тип int.

Имеются следующие операторы отношений:

   
           <    первый операнд меньше второго   
   
           >    первый операнд больше второго   
   
           <=   первый операнд меньше или равен второму   
   
           >=   первый операнд больше или равен второму   
   
           ==   первый операнд равен второму   
   
           !=   первый операнд не равен второму   
Операндами могут быть интегральные типы, типы с плавающей точкой и указатели . Типы сравниваемых операндов могут быть разными. Операторы отношений выполняют обычные арифметические преобразования над операндами целого типа и типа с плавающей точкой. Кроме того, в операторах отношений можно использовать следующие комбинации типов операндов:

Пример 1

Значения x и y равны, поэтому результатом выражения будет 0.

                     int x = 0, y = 0;   
   
                     x < y   

Пример 2

Фрагмент данного примера инициализирует каждый элемент массива array пустой символьной константой.

                     char array[10];   
                     char *p;   
   
                          for (p=array; p<&array[10]; p++)   
                               *p = '\0';   
Пример 3

В данном примере объявляется перечислимая переменная с именем col и признаком color. В любой момент времени переменная может содержать целое значение 0, 1 или 2, которое соответствует одному из элементов перечислимого набора color: red, white или green, соответственно. Если col содержит 0, то выполняется оператор if и все операторы, выполнение которых связано с результатами выполнения if.

                     enum color {red, white, green} col;   
                          .   
                          .   
                          .   
                          if (col == red)   
                          .   
                          .   
                          .   

Побитовая обработка

Операторы побитовой обработки выполняют операции побитового-И (&), вкллючающего-ИЛИ (|) и исключающего-ИЛИ (^). Операнды побитовых операторов должны иметь интегральные типы, но эти типы могут быть разными. Данные операторы выполняют обычные арифметические преобразования, тип результата совпадает с типом операндов после преобразования.

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

   
      &    Оператор побитового-И сравнивает каждый бит своего   
           первого операнда с соответствующим битом его второго   
           операнда. Если оба бита равны 1, то и результирующий   
           бит устанавливается в 1. В противном случае   
           результирующий бит устанавливается в 0.   
   
      |    Оператор побитового включающего-ИЛИ сравнивает каждый   
           бит своего первого операнда с соответствующим битом его   
           второго операнда. Если хотя бы один бит равен 1, то и   
           результирующий бит устанавливается в 1. В противном   
           случае результирующий бит устанавливается в 0.   
   
      ^    Оператор побитового исключающего-ИЛИ сравнивает каждый   
           бит своего первого операнда с соответствующим битом его   
           второго операнда. Если один бит равен 1, а второй бит   
           равен 0, то и  результирующий бит устанавливается в 1.   
           В противном случае результирующий бит устанавливается в   
           0.   
В последующих примерах использованы следующие объявления:

                     short i = 0xab00;   
                     short j = 0xabcd;   
                     short n;   
Пример 1

Результат, присвоенный n, совпадает с i (шестнадцатеричное 0xab00).

                     n = i & j;   

Пример 2

Побитовое включающее-ИЛИ дает в результате 0xabcd (шестнадцатеричное).

                     n = i | j;   

Пример 3

Побитовое исключающее-ИЛИ дает в результате 0xcd (шестнадцатеричное).

                     n = i & j;   

Логические операторы

Логические операторы выполняют операции логического-И (&&) и логического-ИЛИ (||). Операнды логических операций должны иметь интегральный тип, тип плавающей точки или тип указателя. Типы операндов могут быть разными.

Выражения операндов логического-И и логического-ИЛИ вычисляются слева направо. Если значение первого операнда достаточно для определения результата операции, то второй операнд не вычисляется. После первого операнда есть точка упорядочевания.

Логические операторы не проводят обычных арифметических преобразований. Вместо этого они вычисляют каждый операнд в категории его эквивалентности 0.

Результат логической операции 0 или 1. Тип результата int.

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

      &&   Оператор логического-И дает в результате 1, если оба   
           операнда имеют ненулевое значение. Если хотя бы один из   
           операндов равен 0, то результом будет 0. Если первый   
           операнд операции логического-И равен 0, то второй   
           операнд не вычисляется.   
  
      ||   Оператор логического-ИЛИ выполняет операцию   
           включающего-ИЛИ над своими операндами. Результатом   
           будет 0, если оба операнда имеют значение 0. Если хотя   
           бы один из операндов имеет ненулевое значение, то   
           результатом будет 1. Если первый операнд операции   
           логического-ИЛИ имеет ненулевое значение, то второй   
           операнд не вычисляется.   
В последующих примерах использованы данные объявления:

                     int w, x, y, z;   

Пример 1

В данном примере вызывается функция printf для распечатки сообщения, если x меньше y, и y меньше z. Если x больше y, то второй операнд (y<z) не вычисляется и печать сообщения не производится. Обратите внимание на то, что такой порядок действий может вызвать проблемы, когда второй операнд имеет побочные эффекты, которые основаны на некоторых иных соображениях.

                     if (x < y && y < z)   
                          printf("x is less than z\n");   
Пример 2

В данном примере, если x равен w, y или z, то второй аргумент функции printf становится "истина" и будет напечатано значение 1. В противном случае он будет "ложь" и напечатается значение 0. Как только одно из условий будет "истина", дальнейший анализ не проводится.

                     printf("%d", (x==w || x==y || x==z));   

Последовательные вычисления

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

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

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

Пример 1

В данном примере каждый операнд оператора for, который состоит из трех выражений, вычисляется независимо. Сначала вычисляется левый операнд, i+=i, затем правый операнд, j--.

                     for (i=j=1; i+j<20; i+=i, j--);   

Пример 2

В вызове функции func_one передаются три аргумента, разделенные запятыми: x, y+2 и z.

В вызове функции func_two скобки форсируют компилятор интерпретировать первую запятую, как оператор последвательных вычислений. При таком вызове в функцию func_two передаются два аргумента. Первый аргумент является результатом операции последовательного вычисления (x--, y+2), которая имеет значение и тип выражения y+2, а второй аргумент - z.

                     func_one(x, y+2, z);   
                     func_two((x--, y+2), z);   

Условия

В языке С имеется один тернарный оператор: оператор условия (? :). Он имеет следующий вид:

           операнд1 ? операнд2 : операнд3   
Выражение операнд1 должно иметь интегральный тип, тип с плавающей точкой или тип указателя. Оно вычисляется в терминах его эквивалентности 0. За операндом1 следует точка упорядочивания. Процесс вычисления протекает следующим образом: Обратите внимание на то, что вычисляется либо операнд2 либо операнд3, но никогда оба сразу.

Тип результата операции условия зависит от типа операнда2 или операнда3 следующим образом:

Пример 1

В данном примере j присваивается абсолютное значение i. Если i меньше 0, то j присваивается значение -i. Если i больше или равно 0, то j присваивается значение i.

                     j = (i<0) ? (-i) : (i);   

Пример 2

В данном примере объявляются две переменные, x и y, и две функции, f1 и f2. Далее в программе, если зти две переменные имеют одинаковые значения, то вызывается функция f1, в противном случае - f2.

                     void f1(void);   
                     void f2(void);   
                     int x;   
                     int y;   
                          .   
                          .   
                          .   
                     (x==y) ? (f1()) : (f2());   

5.4 Операторы Присвоения

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

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

      ++   унарное увеличение   
      --   унарное уменьшение   
      =    простое присвоение   
      *=   присвоение с умножением   
      /=   присвоение с делением   
      %=   присвоение остатка   
      +=   присвоение со сложением   
      -=   присвоение с вычитанием   
      <<=  присвоение со сдвигом влево   
      >>=  присвоение со сдвигом вправо   
      &=   присвоение с побитовым-И   
      |=   присвоение с побитовым включающим-ИЛИ   
      ^=   присвоение с побитовым исключающим-ИЛИ   
При присвоении тип правого значения преобразуется в тип левого значения. Конкретный способ преобразования, который зависит от двух типов, детально рассматривается в Разделе 5.6.

Выражения локализации

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

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

В языке С следующие выражения могут быть выражениями локализации:

Унарное увеличение и уменьшение

Унарные операторы присвоения (++ и --) соответственно увеличивают и уменьшают значения их операндов. Операнды должны быть интегрального типа, типа с плавающей точкой или типа указателя и должны быть модифицируемыми (не постоянными) выражениями локализации.

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

Увеличенный указатель указывает на следующий объект, а уменьшенный - на предыдущий.

Оператор увеличения (++) или уменьшения (--) может появиться до или после операнда по следующим правилам:

Пример 1

В данном примере переменная pos сравнивается с 0, а затем увеличивается. Если pos до увеличения была положительной величиной, то выполняется следующий оператор. Сначала значение q присваивается p, а затем значения q и p увеличиваются.

                     if (pos++ > 0)   
                          *p++ = *q++;   
Пример 2

В данном примере переменная i уменьшается до ее использования в качестве индекса line.

                     if (line[--i] != '\n')   
                          return;   

Простое присвоение

Оператор простого присвоения присваивает значение своего правого операнда левому операнду. Применимы правила преобразования для присвоений (см. Раздел 5.6.1).

Пример

В данном примере значение y преобразуется в тип double и присваивается x.

                     double x;   
                     int y;   
                     x = y;   

Составные присвоения

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

                     выражение1 += выражение2   
следует понимать, как

                     выражение1 = выражение1 + выражение2   
Однако, составные выражения присвоения не эквивалентны расширенной версии, т.к. выражение составного присвоения вычисляет выражение1 только один раз, а расширенная версия вычисляет выражение1 дважды: в операции сложения и в операции присвоения.

Операнды оператора составного присвоения должны быть интегрального типа или типа с плавающей точкой. Каждый оператор составного присвоения выполняет преобразование так, как соответствующий бинарный оператор обрабатывает и ограничивает типы своих соответствующих операторов. Операторы присвоения со сложением (+=) и присвоения с вычитанием (-=) могут иметь левым операндом тип указателя, в этом случае правый операнд должен быть интегрального типа. Результат операции составного присвоения имеет значение и тип левого операнда.

Пример

В данном примере операция побитового включающего-ИЛИ выполняется над n и MASK, и результат присваивается n. Объявленная константа MASK определяется с директивой предпроцессора #define (данная директива рассматривается в Разделе 8.2.2.).

                     #define MASK 0xff00   
   
                     n &= MASK;   

5.5 Приоритет и порядок проведения операций

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

В Таблице 5.1 показаны приоритеты и ассоциативность операторов языка С. Они приводятся в списке в порядке убывания их приоритета. Если на одной строке указано несколько операторов, то они имеют равный приоритет и вычисляются по правилам их ассоциативности.

      Таблица 5.1. Приоритет и ассоциативность операторов языка С   
   
      Символ         Тип операции                  Ассоциативность   
   
 () [] . ->          выражения                      слева направо   
 - ~ ! * &           унарные                        справа налево   
 ++ -- sizeof тип    унарные                        справа налево   
 * / %               мультипликативные              слева направо   
 + -                 аддитивные                     слева направо   
 << >>               сдвиг                          слева направо   
 < > <= >=           отношения (неравенство)        слева направо   
 == !=               отношения (равенство)          слева направо   
 &                   битовое-И                      слева направо   
 ^                   битовое включающее-ИЛИ         слева направо   
 |                   битовое исключающее-ИЛИ        слева направо   
 &&                  логическое-И                   слева направо   
 ||                  логическое-ИЛИ                 слева направо   
 ?:                  условие                        справа налево   
 = *= /= %=          простые и составные присвоения справа налево   
 += -= <<= >>=       простые и составные присвоения справа налево   
 &= |= ^=            простые и составные присвоения справа налево   
 ,                   последовательные вычисления    слева направо   
Операторы приведены в списке по убыванию их приоритета. Если на одной строке расположено несколько операторов, то они имеют равный приоритет.

Все унарные операторы имеют равный приоритет.

Все простые и составные операторы присвоения имеют равный приоритет.

Как это показано в Таблице 5.1, операнды, состоящие из константы, идентификатора, строки, вызова функции, выражения индекса, выражения выбора компоненты или выражения в скобках имеют наивысший приоритет и ассоциируются слева направо. Преобразования типа и имеют тот же приоритет и ассоциативность, что и унарные операторы.

Выражения могут содержать несколько операторов с одинаковым приоритетом. Если несколько таких операторов появляются в выражении на одном уровне, то их вычисление производится в соответствии с ассоциативностью оператора либо справа налево, либо слева направо. Направление вычислений не влияет на результаты вычисления выражений, которые содержат несколько операторов сложения (+), умножения (*) или обработки бит (& | ^) на одном уровне. Компилятор может вычислять такие выражения в любом порядке, даже если для задания порядка вычислений в выражении заданы скобки. Точками упорядочивания являются только операторы последовательных вычислений (,), логическое-И (&&), логическое-ИЛИ (||), тернарный (?:) и вызовы функций, гарантируя тем самым конкретный порядок вычисления их операндов. Оператор вызова функции это набор скобок, который следует за идентификатором функции. Оператор последовательных вычислений (,) гарантирует вычисление своих операндов слева направо. (Обратите внимание на то, что запятая, которая разделяет аргументы в вызове функции, не является оператором последовательных вычислений и не дает таких гарантий.) Точки упорядочивания рассматриваются в Разделе 5.2.12.

Оператор унарного плюса (+) в определенных ситуациях форсирует группировку. Он реализован синтаксически, но не семантически. Дополнительная информация относительно унарных операторов содержится в Разделе 5.3.2, "Операторы дополнения и унарный плюс".

Логические операторы также гарантируют выполнение своих операндов слева направо. Однако, они вычисляют минимальное число операндов, которое необходимо для определения результата выражения. Например, в выражении x&&y++ второй операнд y++ вычисляется только тогда, когда x есть "истина" (не ноль). Т.о. если x есть "ложь" (0), то значение y не увеличивается.

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

                     a&b||c         (a&b)||c   
                     a=b||c         a=(b||c)   
                     q&&r||s--      (q&&r)||s-­   
В первом выражении оператор битового-И (&) имеет приоритет выше, чем оператор логического-ИЛИ (||), поэтому a&b формирует первый операнд операции логического-ИЛИ.

Во втором выражении оператор логического-ИЛИ (||) имеет приоритет выше, чем оператор простого присвоения (=), поэтому b||c группируется в качестве правого оператора присвоения. Обратите внимание на то, что a будет присвоено значение 0 или 1.

В третьем случае показано корректно сформированное выражение, которое может привести к неожиданному результату. Оператор логического-И (&&) имеет приоритет выше, чем у оператора логического-ИЛИ (||), поэтому q&&r группируется в операнд. Логические операторы гарантируют вычисление своих операндов слева направо, поэтому q&&r будет вычислено раньше, чем s--. Однако, если q&&r даст ненулевое значение, s-- не будет вычислено и значение s не уменьшится. Для устранения проблемы следует поставить s-- первым операндом выражения или уменьшить значение s в отдельной операции.

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

           p==0?p+=1:p+=2      (p==0?p+=1:p)+=2   
В данном выражении оператор равенства (==) имеет высший приоритет, поэтому p==0 группируется в операнд. Следующий приоритет имеет тернарный оператор (?:). Первый его операнд это p==0, а второй - p+=1. Однако, последним операндом тернарного оператора будет p, а не p+=2, т.к. p относится скорее к тернарному оператору, чем к оператору составного присвоения. Возникает синтаксическая ошибка, т.к. +=2 не имеет левого операнда. Для устранения данной ошибки и улучшения восприятия программы следует воспользоваться скобками, например следующим образом:

                (p==0)?(p+=1):(p+=2)   

5.6 Преобразования типа

Преобразование типа производится в следующих случаях:

Преобразования в присвоении

В операциях присвоения тип присваиваемого значение преобразуется в тип переменной, которой делается присвоение. Язык С допускает преобразование типов при присвоении между интегральными типами и типами с плавающей точкой, даже если при преобразовании происходит потеря информации. Используемый метод преобразования зависит от типов, которые участвуют в преобразовании, как это описано в Разделе 5.3.1, "Обычное арифметическое преобразование" и Разделах 5.6.1.1 - 5.6.1.5.

Преобразования из интегральных типов со знаком

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

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

Не происходит потери информации при преобразовании целой со знаком в значение с плавающей точкой. Можно потерять некоторую точность при преобразовании значения long int или unsigned long int в значение float.

В Таблице 5.2 приводятся преобразования интегральных типов со знаком. Предполагается, что тип char по умолчанию имеет знак. Если при компилировании используется опция, которая отменяет умолчание и тип char будет без знака, то вместо Таблицы 5.2 нужно пользоваться строкой для unsigned char из Таблицы 5.3.

      Таблица 5.2. Преобразования из интегральных типов со знаком   
   
 Откуда Куда           Метод   
   
 char   short          расширение знака   
   
 char   long           расширение знака   
   
 char   unsigned char  сохранение образца; бит высшего порядка   
                       теряется, как бит знака   
   
 char   unsigned short расширение знака до short, преобразование   
                       short в unsigned short   
   
 char   unsigned long  расширение знака до long, преобразование   
                       long в unsigned long   
   
 char   float          расширение знака до long, преобразование   
                       long в float   
   
 char   double         расширение знака до long, преобразование   
                       long в double   
   
 char   long  double   расширение знака до long, преобразование   
                       long в double   
   
 short  char           сохранение бита младшего порядка   
   
 short  long           расширение знака   
   
 short  unsigned char  сохранение бита младшего порядка   
   
 short  unsigned short сохранение образца; бит высшего порядка   
                       теряется, как бит знака   
   
 short  unsigned long  расширение знака до long, преобразование   
                       long в unsigned long   
   
 short  float          расширение знака до long, преобразование   
                       long в float   
   
 short  double         расширение знака до long, преобразование   
                       long в double   
   
 short  long  double   расширение знака до long, преобразование   
                       long в double   
   
 long   char           сохранение бита младшего порядка   
   
 long   short          сохранение слова младшего порядка   
   
 long   unsigned char  сохранение байта младшего порядка   
   
 long   unsigned short сохранение слова младшего порядка   
   
 long   unsigned long  сохранение образца бита; бит высшего   
                       порядка теряется, как бит знака   
   
 long   float          представляется float; если long не может   
                       быть точно представлен, то происходит   
                       некоторая потеря точности   
   
 long   double         представляется double; если long не может   
                       быть точно представлен, то происходит   
                       некоторая потеря точности   
   
 long   long double    представляется double; если long не может   
                       быть точно представлен, то происходит   
                       некоторая потеря точности   
В таблице предполагается, что тип char имеет знак.

Примечание

Тип int эквивалентен типу short или типу long в зависимости от реализации. Преобразование значения int происходит аналогично short или long, соответственно.

Преобразования интегральных типов без знака

Целое без знака преобразуется в более короткое целое со знаком или без знака отбрасыванием битов старшего порядка или в большее целое со знаком или без знака дополнением нулей.

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

При преобразовании целого без знака в величину с плавающей точкой оно сначала преобразуется в значение long со знаком, а затем значение long со знаком преобразуется в величину с плавающей точкой.

В Таблице 5.3 содержатся преобразования интегральных типов без знака.

      Таблица 5.3. Преобразования интегральных типов без знака   
   
 Откуда          Куда            Метод   
   
 unsigned char   char            битовая схема сохраняется; бит   
                                 старшего разряда становится битом   
                                 знака   
   
 unsigned char   short           расширяется нулями   
   
 unsigned char   long            расширяется нулями   
   
 unsigned char   unsigned short  расширяется нулями   
   
 unsigned char   unsigned long   расширяется нулями   
   
 unsigned char   float           преобразуется в long; long   
                                 преобразуется в float   
   
 unsigned char   double          преобразуется в long; long   
                                 преобразуется в double   
   
 unsigned char   long double     преобразуется в long; long   
                                 преобразуется в double   
   
 unsigned short  char            сохраняется байт младшего порядка   
   
 unsigned short  short           битовая схема сохраняется; бит   
                                 старшего разряда становится битом   
                                 знака   
   
 unsigned short  long            расширяется нулями   
   
 unsigned short  unsigned char   сохраняется байт младшего порядка   
   
 unsigned short  unsigned long   расширяется нулями   
   
 unsigned short  float           преобразуется в long; long   
                                 преобразуется в float   
   
 unsigned short  double          преобразуется в long; long   
                                 преобразуется в double   
   
 unsigned short  long double     преобразуется в long; long   
                                 преобразуется в double   
   
 unsigned long   char            сохраняется байт младшего порядка   
   
 unsigned long   short           сохраняется слово младшего   
                                 порядка   
   
 unsigned long   long            битовая схема сохраняется; бит   
                                 старшего разряда становится битом   
                                 знака   
   
 unsigned long   unsigned char   сохраняется байт младшего порядка   
   
 unsigned long   unsigned long   сохраняется слово младшего   
                                 порядка   
   
 unsigned long   float           преобразуется в long; long   
                                 преобразуется в float   
   
 unsigned long   double          преобразуется в long; long   
                                 преобразуется в double   
   
 unsigned long   long double     преобразуется в long; long   
                                 преобразуется в double   

Примечание

Тип unsigned int в зависимости от реализации эквивалентен типу unsigned short или unsigned long. Преобразование для unsigned int происходит аналогично unsigned short или unsigned long, соответственно.

Преобразования из unsigned long в float, double или long double не обладают достаточной точностью, если преобразуемое значение больше, чем максимальная положительная величина long.

Преобразования типов с плавающей точкой

Преобразование из типа float в тип double не вносит каких-либо изменений в значение. Преобразование значения из double в float происходит явно, если оно возможно. Может произойти потеря точности, если величина не может быть представлена этим типом.

При преобразовании величины с плавающей точкой в интегральное значение она сначала преобразуется в тип long, а затем из типа long в конкретный интегральный тип, как это показано в Таблице 5.4. При преобразовании в long десятичная часть величины с плавающей точкой отбрасывается. Если при этом результат все еще слишком велик, чтобы поместиться в long, то результат преобразования будет неопределен.

В Таблице 5.4 приводятся преобразования с плавающей точкой

      Таблица 5.4. Преобразования типов с плавающей точкой   
   
 Откуда       Куда            Метод   
   
 float        char            преобразуется в long;   
                              long преобразуется в char   
   
 float        short           преобразуется в long;   
                              long преобразуется в short   
   
 float        long            отбрасывается десятичная точка. Если   
                              результат слишком велик для   
                              представления long,то он неопределен   
   
 float        unsigned short  преобразуется в long;   
                              long преобразуется в unsigned short   
   
 float        unsigned long   преобразуется в long;   
                              long преобразуется в unsigned long   
   
 float        double          сменяется внутреннее представление   
   
 float        long double     сменяется внутреннее представление   
   
 double       char            преобразуется в float;   
                              float преобразуется в char   
   
 double       short           преобразуется в float;   
                              float преобразуется в short   
   
 double       long            отбрасывается десятичная точка. Если   
                              результат слишком велик для   
                              представления long,то он неопределен   
   
 double       unsigned short  преобразуется в long;   
                              long преобразуется в unsigned short   
   
 double       unsigned long   преобразуется в long;   
                              long преобразуется в unsigned long   
   
 double       float           представляется как float. Если   
                              значение double не может быть   
                              представлено, как float, то   
                              происходит потеря точности. Если   
                              значение слишком велико для float,   
                              то результат неопределен.   
   
 long double  char            преобразуется в float;   
                              float преобразуется в char   
   
 long double  short           преобразуется в float;   
                              float преобразуется в short   
   
 long double  long            отбрасывается десятичная точка. Если   
                              результат слишком велик для   
                              представления long,то он неопределен   
   
 long double  unsigned short  преобразуется в long;   
                              long преобразуется в unsigned short   
   
 long double  unsigned long   преобразуется в long;   
                              long преобразуется в unsigned long   
   
 long double  float           представляется как float. Если   
                              значение double не может быть   
                              представлено, как float, то   
                              происходит потеря точности. Если   
                              значение слишком велико для float,   
                              то результат неопределен.   
   
 long double  double          значение long double обрабатывается,   
                              как double   

Примечание

Преобразования из float, double или long double в unsigned long не обеспечивают точности, если преобразуемое значение больше, чем максимальное положительное значение long.

Преобразование типов указателей

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

Пойнтер типа void может быть преобразован в любой тип и в него может быть преобразован любой тип, без каких-либо ограничений.

Преобразования других типов

Тип enum по определению имеет значение int, поэтому преобразование из этого типа и в этот тип будет таким же, как и для int.

Не допускается преобразование между типами структуры и об'единения.

Тип void по определению не имеет значения. Т.о. он не может быть преобразован в какой-либо иной тип, и другие типы не могут быть преобразованы в него присвоением. Однако, можно явно привести значение к типу void, как это рассмотрено в разделе 5.6.2.

Приведения типа

Приведение типов можно использовать для их явного преобразования. Приведение типа имеет вид:

                     (имя-типа)операнд   
где имя-типа это тип и операнд это значение, которое будет преобразовано в заданный тип. (Имена типов были рассмотрены в Разделе 4.9.)

Операнд преобразуется так, как если бы он был присвоен переменной типа имя-типа. Правила преобразования для присвоений (изложенные в Разделе 5.6.1) также применимы и для приведения типов.

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

Преобразования операторов

Преобразования, выполняемые операторами языка С, зависят от оператора и его операнда или операндов. Многие операторы выполняют обычные арифметические преобразования, рассмотренные в Разделе 5.3.1.

Язык С выполняет некоторые арифметические действия с указательами. В арифметике указателей целые значения преобразуются для представления ими позиций памяти. (Дополнительная информация содержится в рассмотрении аддитивных операторов в Разделе 5.3.6, и выражений индексов в Разделе 5.2.5.)

Преобразования вызовов функций

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

Если имеется прототип функции, который включает объявленные типы аргументов, то компилятор выполняет проверку типов. Процесс проверки типов детально рассмотрен в Главе "Функции".

Если прототипа функции нет или в раннем объявлении старого типа опущен список типов аргументов, то проводятся только обычные арифметические преобразования каждого аргумента в вызове функции. Это означает, что величины float преобразуются в double, char или short в int, unsigned char или unsigned short в unsigned int.

Если реализованы специальные ключевые слова near, far и huge, то может быть сделано неявное преобразование значений указателей, передаваемых в функцию. Можно отменить эти неявные преобразования, задав прототип функции и дать тем самым компилятору возможность выполнить проверку типов. Информация о преобразованиях указателей содержится в Вашем Руководстве по компилятору.


Вперед Назад Содержание


Спонсоры:
Слёрм
Inferno Solutions
Hosting by Hoster.ru
Хостинг:

Закладки на сайте
Проследить за страницей
Created 1996-2020 by Maxim Chirkov
Добавить, Поддержать, Вебмастеру