Операции и Выражения С++

1 Лекция №2 5. Операции и Выражения

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

Знаки операций определяют действия, которые должны быть выполнены над операндами.

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

Lvalues  и Rvalues выражения

Lvalues –выражение (или именующее выражение) представляет собой локатор объекта, выражение, которое обозначает объект.

Модифицируемое именующее выражение – это идентифицирующее выражение, относящееся к объекту, к которому возможен доступ и допустимо его изменение в памяти. Так значения констант, описанные в модификаторе const, не являются модифицируемыми именующими выражениями. Исторически в слове Lvalues буква L означает «левый» (Left). Это означает, что Lvalue допускается в левой части оператора присваивания. Здесь в левой части оператора присваивания допустимы только модифицируемые именующие выражения. Например, если а и с – это не являющиеся константами целочисленные идентификаторы с правильно распределенными для них областями памяти, то оба они являются модифицируемыми именующими выражениями, и присваивания типа а=1; с=а+с; вполне допустимы.  

  Rvalues –выражения называют значением переменной (значением правой части выражения). Исторически в слове Rvalues буква R означает «правый» (Right).Так, например, выражение  с+а не является именущим (присваивание типа с+а=с недопустимо), но является   Rvalue –выражением.

Унарные, бинарные и тернарное выражения

 Унарное выражение состоит из операнда и предшествующего ему знаку унарной операции и имеет следующий формат:

ЗнакУнарнойОперации  Выражение .

Бинарное выражения состоит из двух операндов, разделенных знаком бинарной операции:

Выражение1 ЗнакБинарнойОперации Выражение2 .

Тернарное выражение состоит из трех операндов, разделенных знаками тернарной операции (?) и (:), и имеет формат:

Выражение1 ? операнд2 : операнд3 .

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

Унарные операции выполняются справа налево.

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

Таблица 7   Унарные операции         

Знак операцииОперацияГруппа операций+Унарный плюсАддитивные-Отрицание++Увеличение --Уменьшение~Поразрядное отрицание(дополнение)Поразрядные операции!Логическое отрицаниеЛогические операции*Разадресация (косвенная адресация)Адресные  операции&Вычисление адресаSizeofРазмерSize-операция

В отличие от унарных, бинарные операции, список которых приведен в табл.8, выполняются слева направо.

Таблица 8   Бинарные операции         

Знак операцииОперацияГруппа операций*УмножениеМультипликативные/Деление%Остаток от деления+СложениеАддитивные-Вычитание<<Сдвиг влевоОперации сдвига>>Сдвиг вправо<МеньшеОперации отношения<=Меньше или равно>=Больше или равно==Равно!=Не равно&Поразрядное ИПоразрядные операции|Поразрядное ИЛИ^Поразрядное исключающее ИЛИ&&Логическое ИЛогические операции||Логическое ИЛИ,Последовательное вычислениеПоследовательного вычисления=ПрисваиваниеОперации присваивания*=Умножение с присваиванием/=Деление с присваиванием%=Остаток от деления с присваиванием-=Вычитание с присваиванием+=Сложение с присваиванием<<=Сдвиг влево с присваиванием>>=Сдвиг вправо присваиванием&=Поразрядное И с присваиванием|=Поразрядное ИЛИ с присваиванием^=Поразрядное исключающее ИЛИ с присваиванием

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

Приоритеты операций и порядок вычислений

В языке С++ операции с высшими приоритетами вычисляются первыми.  Приоритеты в порядке убывания и порядок выполнения операций приведены в табл. 9.

Таблица 9 

ПриоритетЗнак операцииТипы операции Порядок выполнения1() [] . ->ВыражениеСлева направо2- + ~ ! * & ++ -- sizeof приведение типов УнарныеСправа налево3* / %МультипликативныеСлева направо4+ -Аддитивные5<< >>Сдвиг6< > <= >=Отношение7== !=Отношение (равенство)8&Поразрядное И9^Поразрядное исключающее ИЛИ10|Поразрядное ИЛИ11&&Логическое И12||Логическое ИЛИ13? :Условная14= *= /= %= += -= &= |= >>= <<= ^=Простое и составное присваиваниеСправа налево15,Последовательное вычислениеСлева направо

Арифметические преобразования при вычислении выражений

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

Рассмотрим общие арифметические преобразования.

1. Операнды типа float преобразуются к типу double.

2. Если один операнд 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, то второй операнд преобразуется к этому же типу.

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

Пример:

double         ft,sd;

unsigned char  ch;  unsigned long  in;

int            i;

       ....

sd=ft*(i+ch/in);

При выполнении оператора присваивания правила преобразования будут использоваться следующим образом. Операнд ch преобразуется к unsigned int (правило 5). Затем он преобразуется к типу unsigned long (правило 6). По этому же правилу i преобразуется к unsigned long и результат операции, заключенной в круглые скобки будет иметь тип unsigned long. Затем он преобразуется к типу double (правило 3) и результат всего выражения будет иметь тип double.

Пример:

#include<stdio.h>

main()

{

double b=2.1;

int a=3;

double d1=b*a*a/2;

double d2=b*(a*a/2);

printf("%f  %f",d1,d2);

}

 даст результат d1=9.45 и  d2=8.4, т.к. в первом случае переменная а при умножении на переменную b преобразуется к типу double, после чего происходит деление на 2.  Во втором случае сначала выполняется вычисление выражения  а*а целого типа, затем деление на 2 с получением целого результата, а только затем умножение на b, что приводит к типу double. Поэтому для получения верного результата во втором случае необходимо выполнить деление не на 2, а на 2.0.

   При преобразовании символьных переменных в целые возникает один тонкий момент. Дело в том, что сам язык не указывает, должны ли переменным типа char соответствовать численные значения со знаком или без знака. Может ли при преобразовании char в int получиться отрицательное целое? К сожалению, ответ на этот вопрос меняется от машины к машине, отражая расхождения в их архитектуре. На некоторых машинах (pdp-11, например) переменная типа char, крайний левый бит которой содержит 1, преобразуется в отрицательное целое ("знаковое расширение"). На других машинах такое преобразование сопровождается добавлением нулей с левого края, в результате чего всегда получается положительное число.

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

  Наиболее типичным примером возникновения такой ситуации является сучай, когда значение 1 используется в качестве EOF. Рассмотрим программу

char            c; c = getchar();

if (c == EOF)

 ...

  На машине, которая не осуществляет знакового расширения, переменная 'c' всегда положительна, поскольку она описана как char, а так как EOF отрицательно, то условие никогда не выполняется. Чтобы избежать такой ситуации, мы всегда предусмотрительно использовали int вместо char для любой переменной, получающей значение от getchar.

   Основная же причина использования int вместо char не связана с каким-либо вопросом о возможном знаковом расширении. Просто функция getchar должна передавать все возможные символы (чтобы ее можно было использовать для произвольного ввода) и, кроме того, отличающееся значение EOF. Следовательно значение EOF не может быть представлено как char, а должно храниться как int.

   Другой полезной формой автоматического преобразования типов является то, что выражения отношения, подобные i>j, и логические выражения, связанные операциями && и ||, по определению имеют значение 1, если они истинны, и 0, если они ложны. Таким образом, присваивание

    isdigit = c >= '0' && c <= '9';

полагает isdigit равным 1, если с - цифра, и равным 0 в противном случае. (в проверочной части операторов if, while, for и т.д. "истинно" просто означает "не нуль").

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

5.1. Мультипликативные и аддитивные операции

Язык С++ поддерживает стандартный набор арифметических операций:

умножение /*/

деление (/)

взятие остатка от деления(%)

сложение (+)

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

С++ поддерживает операцию унарного минуса (a+(-b)), которая выполняет дополнение до двух, а также, в качестве расширения по стандарту ANSI, операцию унарного плюса: (a+(+b)).

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

Мультипликативные операции *, / и % группируют слева направо. Выполняются обычные арифметические преобразования.

Форма записи:

    выражение * выражение

    выражение  /  выражение

    выражение  % выражение

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

Пример:

    int  i=5;

    float f=0.2;

    double g,z;

        g=f*i;

Бинарная операция / определяет деление. При делении положительных целых округление осуществляется в сторону 0, но если какой-либо из операндов отрицателен, то форма округления является машинно-зависимой. На всех машинах, охватываемых данным руководством, остаток имеет тот же знак, что и делимое. Всегда истинно, что (a/b)*b + a%b равно a (если b не 0).

Пример:

    int i=49, j=10,  n, m;

    n = i/j;                 /* результат   4   */

    m = i/(-j);              /* результат  -4   */

Бинарная операция % дает остаток от деления первого выражения на второе.    

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

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

Пример:

    int n = 49, m = 10, i, j, k, l;

    i = n % m;             /*    9    */

    j = n % (-m);          /*    9    */

    k = (-n) % m;          /*    -9   */

    l = (-n) % (-m);       /*    -9   */

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

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

Форма записи:

   выражение + выражение

   выражение  -  выражение

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

Пример:

    int   i=30000, j=30000, k;

           k=i+j;

В результате сложения k получит значение равное -5536.

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

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

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

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

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

1. Оба операнда целого или плавающего типа.

2. Оба операнда являются указателями на один и тот же тип.

3. Первый операнд является указателем, а второй - целым.

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

 Пример:

    double d[10],* u;

    int i;

    u = d+2;  /*  u  указывает на третий элемент массива */

    i = u-d;  /*  i  принимает значение равное 2         */

Аддитивные унарные операции + и - группируют справа  налево  .

 Форма записи:

    + выражение

    -  выражение

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

Операции инкремента /++/ и декремента /--/

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

  Если знак операции стоит перед операндом (префиксная форма записи), то изменение операнда происходит до его использования в выражении и результатом операции является увеличенное или уменьшенное значение операнда.

Форма записи:

 ++  Выражение;

 --  Выражение.

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

Форма записи:

Выражение  ++;

Выражение  --

Пример:

     int t=1, s=2, z, f;  z=(t++)*5;

  Вначале происходит умножение t*5, а затем увеличение t. В результате получится z=5, t=2.

          f=(++s)/3;

Вначале значение s увеличивается, а затем используется в операции деления. В результате получим s=3, f=1.

Пример:

sum = a + b ++;

sum = a + ++b;

  Первая строка производит следующие действия: складываются значения переменных а и b, результат вычисления присваивается переменной sum, затем переменная b увеличивается на единицу. Вторая строка делает следующее: значение  b  увеличивается  на единицу, а складывается с b, результат сложения присваивается переменной sum.

5.2. Операции отношения

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

                           >=     больше чем или равно

                           <      меньше чем

                           <=     меньше чем или равно

                           ==      равно

                           !=     не равно

Операции сравнения

Операции сравнения группируют слева направо, но этот факт не очень-то полезен: a < b < c не означает то, чем кажется.

Форма записи:

     выражение       <       выражение

     выражение       >       выражение

     выражение       <=      выражение

     выражение       >=      выражение    

Операции < (меньше чем), > (больше чем), <= и >= все дают 0, если заданное соотношение ложно, и 1, если оно истинно. Тип результата int. Выполняются обычные арифметические преобразования. Могут сравниваться два указателя; результат зависит от относительного положения объектов, на которые указывают указатели, в адресном пространстве. Сравнение указателей переносимо только если указатели указывают на объекты одного массива.

Пример:

#include<stdio.h>

void main()

{

  float a=3.4, b=7.2;

  int    a1=4, b1=4;

printf(“%d\n”, a>b);

printf(“%d\n”, a<=b);

printf(“%d\n”, a1<b1);

printf(“%d\n”, a1>=b1);

}

Результат:

0

1

0

1

Операции равенства

Операции == и != в точности аналогичны операциям сравнения за исключением их низкого приоритета. (Так, a < b == c < d есть 1 всегда, когда a < b и c < d имеют одинаковое истинностное значение.)

Форма записи:

выражение    ==   выражение

выражение    !=   выражение

Указатель может сравниваться с 0.

Пример:

#include<stdio.h>

void main()

{

    int    a=7, b=9;

printf(“%d\n”, a==b);

printf(“%d\n”, a!=b);

}

Результат:

0

1

5.3.Логические операции

В языке С++ имеются две  логические  константы:  1 (TRUE, истина) и 0 (FALSE, ложь). Логическая переменная или константа принимает одно.

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

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

||  (OR)  логическое сложение (ИЛИ),

&&  (AND)  логическое умножение (И),

!  (NOT)  логическое отрицание (НЕ).

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

Логическое умножение (&&)  дает истинный  результат  только  в том случае, если обе величины истинны. Если  хотя бы одна  величина ложна, то результат также будет ложным.

Логическое отрицание  (!) дает истинный результат, если операнд есть ложь (равен нулю).

0<1   FALSE-0;TRUE-1

      Таблица истинности логических операций

AB&&(И)||(ИЛИ)! А(НЕ)00110101000101111100

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

Операция логическое И

Форма записи:

выражение && выражение

Операция && группирует слева направо. Она возвращает 1, если оба операнда ненулевые, и 0 в противном случае. В противоположность операции & операция && гарантирует вычисление слева направо; более того, второй операнд не вычисляется, если первый операнд есть 0. Например, если b равно 0, равенство не проверяется и деления на ноль не происходит:

if (b&&a/b==c){…}

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

Пример:

Определить, находиться ли точка С на отрезке АВ:

#include<stdio.h>

void main()

{

  float a=2.1, b=7.4,c;

  printf(“Введите с\n”);

  scanf(“%f”,&c);

printf(“%d”,(a<c)&&(c<b));

}

Результат:

Введите с

3

1

Операция логическое ИЛИ

Форма записи:

выражение || выражение

  Операция || группирует слева направо. Она возвращает 1, если хотя бы один из ее операндов ненулевой, и 0 в противном случае. В противоположность операции | операция || гарантирует вычисление слева направо; более того, второй операнд не вычисляется, если первый операнд не есть 0.

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

Пример:

Определить, не находиться ли точка С на отрезке АВ:

#include<stdio.h>

void main()

{  float a=2.1, b=7.4,c;

  printf(“Введите с\n”);

  scanf(“%f”,&c);

printf(“%d”,(c<a)||(c>b));

}

Результат:

Введите с

3

0

Операция логическое НЕ

Форма записи:

! выражение

 Операция логического отрицания «НЕ» (!) вырабатывает значение 0, если операнд есть истина (не нуль), и значение 1, если операнд равен нулю (0). Результат имеет тип int. Операнд должен быть целого или плавающего типа или типа указатель.

Пример:

int t, z=0;

t=!z;

Переменная t получит значение равное 1, так как переменная z имела значение равное 0 (ложно).

Пример:

Определить, находиться ли точка С на отрезке АВ:

#include<stdio.h>

void main()

{  float a=2.1, b=7.4,c;

  printf(“Введите с\n”);

  scanf(“%f”,&c);

printf(“%d”,(!(c<a))||(!(c>b)));

}

Результат:

Введите с

3

1

5.4. Побитовые операции

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

сдвиг влево (<<)

сдвиг вправо (>>)

И (&)

ИЛИ (I)

исключающее ИЛИ (^),

дополнение НЕ(~).

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

Операции сдвига

Операции сдвига << и >> группируют слева направо. Обе выполняют одно обычное арифметическое преобразование над своими операндами, каждый из которых должен быть целым. В этом случае правый операнд преобразуется к типу int; тип результата совпадает с типом левого операнда. Результат не определен, если правый операнд отрицателен или больше или равен длине объекта в битах.

Форма записи:

     выражение      <<      выражение

     выражение      >>      выражение

Значением Е1 << Е2 является Е1 (рассматриваемое как битовое представление), сдвинутое влево на Е2 битов; освободившиеся биты заполняются нулями. Значением Е1 >> Е2 является Е1 , сдвинутое вправо на Е2 битовых позиций. Гарантируется, что сдвиг вправо является логическим (заполнение нулями), если Е1 является unsigned; в противном случае он может быть арифметическим (заполнение копией знакового бита).

Отметим, что сдвиг влево соответствует умножению первого операнда на степень числа 2, равную второму операнду, а сдвиг вправо соответствует делению первого операнда на 2 в степени, равной второму операнду.

Примеры:

     int  i=0x1234, j,  k ;    

     k = i<<8 ;         /*    i = 0x34   */

i     0001 0010 0011 1000

i<<8  0011 1000 0000 0000    -0x34    (логический сдвиг)   

i>>8  0000 0000 0001 0010    -0x0012  

-i>>8  1111 1111 0001 0010    -0xff12 (арифметический сдвиг)

Операция побитовое И

Форма записи:

выражение & выражение

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

Пример.

    int  i=0x45FF,     /*      i= 0100 0101 1111 1111   */

         j=0x00FF;             j= 0000 0000 1111 1111   */

    char r;

      r = i&j;      /* r=0x00FF = 0000 0000 1111 1111    */

Операция побитовое исключающее ИЛИ

Форма записи:

выражение ^ выражение

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

AB^( исключающее ИЛИ)001101010110

Пример.

    int  i=0x45FF,     /*      i= 0100 0101 1111 1111   */

         j=0x00FF;             j= 0000 0000 1111 1111   */

    char r;

      r = i^j;      /* r=0x4500 = 0100 0101 0000 0000   */

Операция побитовое включающее ИЛИ

Форма записи:

выражение | выражение

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

Пример.

    int  i=0x45FF,     /*      i= 0100 0101 1111 1111   */

         j=0x00FF;             j= 0000 0000 1111 1111   */

    char r;

      r = i|j;      /* r=0x45FF = 0100 0101 1111 1111   */

Операция побитового отрицания НЕ

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

Форма записи:

 ~  Выражение;

Пример:

      char            b = '9';

      unsigned char   f;

b = ~f;

Шестнадцатеричное значение символа '9' равно 39. В результате операции ~f будет получено шестнадцатеричное значение С6, что соответствует символу 'ц'.

В шестнадцатеричном представлении:

39    0011  1001   Если выполнить побитовое отрицание, то получится

С6   1100  0110.

Отличие логических операций от логических побитовых операций

Логические операторы И /&&/, ИЛИ /||/ необходимо  отличать  от  описанных  выше  побитовых  операторов /&,I/.  Логические  операторы  работают  с логическими значениями /true и false/, позволяя составлять выражения отношения.

Чем они отличаются от соответствующих побитовых операторов?

Логические операторы дают результат или  равный  1  /истина/ или  0 /ложь/, тогда как побитовые операторы производят соответствующие действия над каждым битом;

Логические операторы && и ||  являются  «короткозамкнутыми». Предположим, имеется выражение: expI&& exp2. Если exp1 ложно, тогда ехр2 вычисляться не будет. Точно также имея выражение ехр1 || exp2, мы не вычислим exp2, если exp1 истинно.

5.5. Операция последовательного вычисления

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

Форма записи:

выражение , выражение

Например,  если  имеются  две переменные Т и Х, обе типа int, тогда выражение

P=(Т=3,Х=Т+2)

производит следующие действия: переменной Т присваивается значение 3, затем к 3 прибавляется  2, и полученное значение присваивается переменной Х, затем все выражение  P принимает значение переменной Х.

5.6. Операции присваивания

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

Форма записи:

  ИдентификаторПеременной  ОперацияПрисваивания  Выражение

ОперацииПрисваивания:

    =  +=  -=  *=  /=  %=  >>=  <<=  &=  ~=  |=

  При присваиваниях осуществляется преобразование типов; значение правой части преобразуется к типу левой, который и является типом результата (см. арифметические преобразования) . Символьные переменные преобразуются в целые либо со знаковым расширением, либо без него, как описано выше. Обратное преобразование int в char ведет себя хорошо - лишние биты высокого порядка просто отбрасываются. Таким образом

int             i;

char            c;

.  .  .

i = c;

c = i;

значение 'c' не изменяется. Это верно независимо от того, вовлекается ли знаковое расширение или нет.

 Если x типа float, а i типа int, то как

       x = i;

так и

       i = x;

приводят к преобразованиям; при этом float преобразуется в int отбрасыванием дробной части. Тип double преобразуется во float округлением. Длинные целые преобразуются в более короткие целые и в переменные типа char посредством отбрасывания лишних битов высокого порядка.

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

  Операция простого присваивания’ = ’ используется для замены значения левого операнда, значением правого операнда. При присваивании производится преобразование типа правого операнда к типу левого операнда по правилам, упомянутым раньше. Левый операнд должен быть модифицируемым.

Пример:

      int t;

      char f;

      long z;

      t=f+z;

  Значение переменной f преобразуется к типу long, вычисляется f+z ,результат преобразуется к типу int и затем присваивается переменной t.

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

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

ИдентификаторПеременной  БинарнаяОперация = Выражение

Допустимые БинарныеОперации:

      +  -  *  /  %  >>  <<  &  ~  |

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

ИдентификаторПеременной = Выражение1 БинарнаяОперация Выражение2.

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

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

Примеры:

    double  a=5.2;

    double  b=3.0;

    b+=a;      /*  эквивалентно   b=b+a */

    a/=b+1;    /*  эквивалентно   a=a/(b+1)*/

    a-=4;      /*  эквивалентно   a=a-4*/

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

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

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

           prog (a,a=k*2);

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

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

Например, выражение i*j+(j++)+(--i) может принимать различные значения при обработке разными компиляторами. Чтобы избежать недоразумений при выполнении побочных эффектов необходимо придерживаться следующих правил.

 1. Не использовать операции присваивания переменной в вызове функции, если эта переменная участвует в формировании других аргументов функции.

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

5.7. Адресные операции

    Язык С++ поддерживает два адресных оператора:  операция  взятия  адреса & и косвенная операция *. Эти операции используются для работы с переменными типа указатель.

УКАЗАТЕЛЬ -  это переменная, содержащая адрес некоторых данных, а не их значение. Зачем это нужно?                                    

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

        Во-вторых, использование  указателей  позволит вам создавать новые переменные в процессе выполнения программы.   С++  позволяет  вашей программе   запрашивать некоторое количество памяти (в байтах), возвращая адреса, которые можно запомнить в указателе. Этот  прием известен как ДИНАМИЧЕСКОЕ РАСПРЕДЕЛЕНИЕ; используя его, ваша программа может приспосабливаться к любому объему  памяти,   в  зависимости от  того как много (или мало)  памяти доступно вашему  компьютеру.

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

Операция разадресации

 Форма записи:

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

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

Типом результата является тип величины, адресуемой указателем. Результат не определен, если указатель содержит недопустимый адрес.

Рассмотрим типичные ситуации, когда указатель содержит недопустимый адрес:

• указатель является нулевым;

• указатель определяет адрес такого объекта, который не является активным в момент ссылки;

• указатель определяет адрес, который не выровнен до типа объекта, на который он указывает;

• указатель определяет адрес, не используемый выполняющейся программой.

Операция взятия адреса

 Форма записи:

 & Идентификатор;

  Операция адрес (&) дает адрес своего операнда: если sum есть переменная типа int, тогда &sum есть адрес /ячейка памяти/  этой  переменной.  Таким  образом, если msg есть ссылка на переменную типа char, тогда *msg есть символ, на который указывает msg.

 Операндом может быть любое именуемое выражение. Имя функции или массива также может быть операндом операции «адрес», хотя в этом случае знак операции является лишним, так как имена массивов и функций являются адресами. Результатом операции адрес является указатель на операнд. Тип, адресуемый указателем, является типом операнда.

  Операция адрес не может применяться к элементам структуры, являющимися полями битов, и к объектам с классом памяти register.

  Рассмотрим следующую программу:                                       

                 void main()                                                        

                 {                                                             

                   int ivar,*iptr;                                             

                                                                               

                   iptr = &ivar;                                               

                   ivar = 421;                                                 

                   printf("Размещение ivar: %p\n",&ivar);                      

                   printf("Содержимое ivar: %d\n", ivar);                      

                   printf("Содержимое iptr: %p\n", iptr);                      

                   printf("Адресуемое значение: %d\n",*iptr);                  

                 }                                                             

         В ней объявлены две переменные:  ivar и iptr. Первая, ivar -  это целая переменная,  т.е. содержащая значение типа int. Вторая,   iptr - это указатель на целую переменную,  следовательно, она  содержит АДРЕС значения типа int. Можно также сказать, что переменная iptr - это указатель,  так как перед ее описанием стоит звездочка

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

            - адрес переменной ivar присваисвается iptr                        

            - целое значение 421 присваивается ivar                            

Адресный оператор  (&) позволяет получить адрес,  по которому размещено значение   переменной ivar.

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

            Размещение ivar: 166E                                              

            Содержимое ivar: 421                                               

            Содержимое iptr: 166E                                              

            Адресуемое значение: 421                                           

                                                                               

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

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

         Рассмотрим теперь небольшую вариацию предыдущей программы:                                                                                         

                 void main()                                                        

                 {                                                             

                     int ivar,*iptr;                                           

                                                                              

                     iptr  = &ivar;                                            

                     *iptr = 421;                                              

                   printf("Размещение ivar: %p\n",&ivar);                      

                   printf("Содержимое ivar: %d\n", ivar);                      

                   printf("Содержимое iptr: %p\n", iptr);                      

                   printf("Адресуемое значение: %d\n",*iptr);                  

                 }                                                             

                                                                               

          В этой  программе также адрес переменной ivar присваивается  iptr, но  вместо присваивания числа 421 переменной ivar, это значение присваивается по указателю *iptr.  Каков результат ?  Точно  такой же, как и в предыдущей программе. Почему ? Потому что выражения *iptr   и  ivar суть одна и та же ячейка памяти - поэтому в этом случае оба оператора заносят значение 421 в  одну  и  ту  же  ячейку памяти.

5.8. Операция sizeof

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

               sizeof(операнд)

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

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

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

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

Например:

#include<stdio.h>

void main()

{

int a;

double b;

printf("%d",sizeof(a*b));

}

даст результат 8 байт такой, если бы было записано sizeof(double) , т.к. результат выражения a*b приводится к типу  double.

   Если в качестве операнда указано имя массива, то результатом является размер всего массива (т.е. произведение числа элементов на длину типа), а не размер указателя, соответствующего идентификатору массива.

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

Пример:

     struct     { char   h;

                  int    b;

double f;

} str;

int a1;

a1  = sizeof(str);

   Переменная а1 получит значение, равное 12, в то же время если сложить длины всех используемых в структуре типов, то получим, что длина структуры str равна 7.

  Несоответствие имеет место в виду того, что после размещения в памяти первой переменной h длинной 1 байт, добавляется 1 байт для выравнивания адреса переменной b на границу слова (слово имеет длину 2 байта для машин серии IBM PC AT /286/287), далее осуществляется выравнивание адреса переменной f на границу двойного слова (4 байта), таким образом в результате операций выравнивания для размещения структуры в оперативной памяти требуется на 5 байт больше.

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

struct  { double f;

              int    b;

              char   h;

} str;

5.9. Условная операция

В языке С++ имеется одна тернарная операция - условная операция, которая имеет следующий формат:

Форма записи:

выражение1 ? выражение2 : выражение3

Результат Выражения1 должен быть целого или плавающего типа или быть указателем. Он оценивается с точки зрения его эквивалентности 0. Если результат Выражения1 не равен 0, то вычисляется Выражение2 и его значение является результатом операции. Если результат Выражения1 равен 0, то вычисляется Выражение3 и его значение является результатом операции. Следует отметить, что вычисляется либо Выражение2, либо Выражение3, но не оба. Тип результата зависит от типов 2 и 3, следующим образом.

  1. Если результат Выражения2 или Выражения3 имеет целый или плавающий тип (отметим, что их типы могут отличаться), то выполняются обычные арифметические преобразования. Типом результата является тип результата выражения после преобразования.

  2. Если Выражение2 и Выражение3 имеют один и тот же тип структуры, объединения или указателя, то тип результата будет тем же самым типом структуры, объединения или указателя.

  3. Если оба выражения имеют тип void, то результат имеет тип void.

  4. Если одно выражение является указателем на объект любого типа, а другое является указателем на vold, то указатель на объект преобразуется к указателю на vold, который и будет типом результата.

  5. Если одно выражение  является указателем, а другое константным выражением со значением 0, то типом результата будет тип указателя.

Пример:

#include <stdio.h>

void main()

{

 float a,b,c,max,m1;

   printf("Введите а,b,с:\n");

   scanf("%f %f %f",&a,&b,&c);

   max=(a>b)?a:b;

   max=(max>c)?max:c;

    printf("%f\n",max);

 }

Переменной max присваивается максимальное значение переменных a, b, c.

Пример: Расположить числа a,b,c в порядке убывания

#include <stdio.h>

void main()

{

 float a,b,c,h;

     printf("Введите a,b,c:\n");

     scanf("%f %f %f",&a,&b,&c);

     a<b?h=a,a=b,b=h:h=0;

     a<c?h=a,a=c,c=h:h=0;

     b<c?h=b,b=c,c=h:h=0;

     printf("a= %.3f b= %.3f  c= %.3f\n",a,b,c);

     printf("\nПРОГРАММУ СОСТАВИЛ ИВАНОВ");

}

5.10. Операции преобразования типов

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

- при выполнении операций осуществляются обычные арифметические преобразования (которые были рассмотрены выше, см. #2.3);

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

- при передаче аргументов функции.

 Кроме того, в С++ есть возможность явного приведения значения одного типа к другому.

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

  Допускается преобразования целых и плавающих типов, даже если такое преобразование ведет к потере информации:

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

1)Преобразование целых типов со знаком.

а) Целое со знаком преобразуется к более короткому целому со знаком, посредством усечения старших битов.

б) Целое со знаком преобразуется к более длинному целому со знаком, путем размножения знака.

в) При преобразовании целого со знаком к целому без знака, целое со знаком преобразуется к размеру целого без знака и результат рассматривается как значение без знака.

г) Преобразование целого со знаком к плавающему типу происходит без потери информации, за исключением случая преобразования значения типа long int или unsigned long int к типу float, когда точность часто может быть потеряна.

2) Преобразование целых типов без знака.

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

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

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

г) Целые значения без знака преобразуются к плавающему типу, путем преобразования целого без знака к значению типа signed long, а затем значение signed long преобразуется в плавающий тип. Преобразования из unsigned long к типу float, double или long double производятся с потерей информации, если преобразуемое значение больше, чем максимальное положительное значение, которое может быть представлено для типа long.

3) Преобразования плавающих типов.

а) Величины типа float преобразуются к типу double без изменения значения.

б) Величины double и long double преобразуются к float c некоторой потерей точности. Если значение слишком велико для float, то происходит потеря значимости, о чем сообщается во время выполнения.

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

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

4) Преобразование типов указателя.

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

б) Указатель на тип void может быть преобразован к указателю на любой тип, и указатель на любой тип может быть преобразован к указателю на тип void без ограничений. Значение указателя может быть преобразовано к целой величине. Метод преобразования зависит от размера указателя и размера целого типа следующим образом:

- если размер указателя меньше размера целого типа или равен ему, то указатель преобразуется точно так же, как целое без знака;

- если указатель больше, чем размер целого типа, то указатель сначала преобразуется к указателю с тем же размером, что и целый тип, и затем преобразуется к целому типу.

5) Целый тип может быть преобразован к адресному типу по следующим правилам:

- если целый тип того же размера, что и указатель, то целая величина просто рассматривается как указатель (целое без знака);

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

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

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

Эти преобразования выполняются независимо для каждого аргумента. Величины типа float преобразуются к double, величины типа char и short преобразуются к int, величины типов unsigned char и unsigned short преобразуются к unsigned int. Могут быть также выполнены неявные преобразования переменных типа указатель. Задавая прототипы функций, можно переопределить эти неявные преобразования и позволить компилятору выполнить контроль типов.

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

( ИмяТипа )  Выражение

или альтернативная форма записи

ИмяТипа  ( Выражение)

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

  Например, библиотечная процедура sqrt ожидает аргумента типа double и выдаст бессмысленный ответ, если к ней по небрежности обратятся с чем-нибудь иным. таким образом, если N - целое, то выражение

      sqrt((double) N)

 до передачи аргумента функции sqrt преобразует N к типу double. (Отметим, что операция приведения типов преобразует значение N в надлежащий тип; фактическое содержание переменной N при этом не изменяется).

Пример:

        int     i=2;

        long    l=2;

        double    d;

        float     f;

        d=(double)i * (double)l;

        f=(float)d;

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

5.11 Особые операции

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

первичное_выражение ( список_выражений opt )

и индексирование

первичное_выражение [ выражение ]

считаются бинарными операциями. Именами определяющей функции являются соответственно operator() и operator[]. Обращение x(arg) интерпретируется как x.operator()(arg) для классового объекта x. Индексирование x[y] интерпретируется как x.operator[](y).

А

В

А

А

В

← Предыдущая
Страница 1
Следующая →

Файл

Л2.doc

Л2.doc
Размер: 263.5 Кб

.

Пожаловаться на материал

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

У нас самая большая информационная база в рунете, поэтому Вы всегда можете найти походите запросы

Искать ещё по теме...

Похожие материалы:

Радиоприемные устройства цифровых систем радиосвязи

Целью данного курсового проекта является: 1) освоение методики расчета шумовых параметров РПМУ в целом; 2) освоение методики расчета избирательности РПМУ в целом, так  и отдельных его каскадов

Публичная собственность: государственная и муниципальная собственность

Право государственной и муниципальной собственности. Разграничение объектов государственной и муниципальной собственности Осуществление полномочий публичного собственника Приватизация государственного и муниципального имущества

Грыжи. Бронхиальная астма

Грыжи. Этиология и патогенез. Классификация. Симптоматика. Осложненные грыжи. Отдельные формы грыж. Особенности грыж у детей. Лечение и профилактика. Прогноз. Экспертиза. Бронхиальная астма: определение, этиология, патогенез, классификация, клиника, диагностика, дифференциальная диагностика, современные принципы лечения, профилактика. Неотложная помощь при астматическом статусе. Врачебная тактика.

Гідрогеологічні дослідження при пошуково-розвідувальних роботах та експлуатації нафтових і газових родовищ

Головні типи нафтогазоносних басейнів і родовищ нафти й газу. Етапи та стадії пошуково-розвідувальних робіт на нафту і газ. Гідрогеологічні критерії оцінки перспектив нафтогазоводоносних басейнів. Гідрогеологічні дослідження при пошуках нафтових і газових родовищ. Гідрогеологічні дослідження при розвідці й розробці нафтових і газових родовищ.

Попутные нефтяные газы. Вопросы к экзамену

Алканы. Строение, изомерия, номенклатура, способы получения. Ароматические углеводороды. Химические свойства бензола и его гомологов. Сложные эфиры. Изомерия и номенклатура. Получение, свойства, применение.

Сохранить?

Пропустить...

Введите код

Ok