Операции и выражения
Несмотря на ограниченный набор базовых типов данных (целые и вещественные арифметические данные и строковые литералы), в языке C++ определен обширный набор операций над данными, часть из которых непосредственно соответствует машинным командам. Операции служат для построения выражений. Выражение - последовательность операндов и знаков операций и служит для вычисления некоторого значения. В качестве операндов в выражении выступают идентификаторы переменных, константы и строковые литералы, являющиеся первичными выражениями. Выражение, заключенное в круглые скобки, рассматривается как первичное. Каждая операция предполагает использование определенных типов операндов (целых, вещественных, указателей). Операция присваивания является выражением, поэтому различаются операнды, которым можно присвоить новое значение, и операнды, значения которых не могут меняться. Чтобы операнду можно было присвоить значение, ему должна соответствовать область памяти, и компилятору должен быть известен адрес этой области. Такие операнды называют L-выражениями, т.к. они могут быть записаны в левой части оператора присваивания. Операция ":: " (два двоеточия) применяется для уточнения имени объекта программы в случае, когда в этом месте программы известны два одинаковых имени, например, когда одно имя объявлено глобально, а другое - в теле функции. Если имени предшествуют два двоеточия, то это глобальное имя. Результат вычисления выражения зависит от приоритетов операций. В C++ реализована система приоритетов операций, включающая 16 уровней. В табл.2 приведен перечень операций с указанием их приоритетов, назначения и схемы записи Таблица 4.2 Операции в C++
Обращение к членам структуры или объединения - имя структурного данного либо указателя на структурное данное. В первом случае полное имя члена структуры состоит из имени самой структуры и имени члена структуры, разделенных точкой. Во втором случае за именем указателя на структуру ставится знак -> (стрелка), а за ним - имя члена структуры. Например, пусть в программе объявлен структурный тип Аn-Struct, содержащий компоненту с именем mmb типа int, и объявлены такие объекты: AnStruct st1; //Данное s1 типа AnStruct AnStruct *pst1 = Sst1; //Указатель на //данное типа AnStruct Тогда к члену структуры mmb из st1 можно обратиться как к st1.mmb или как pst1-> mmb. Поскольку членом структуры может быть указатель, в C++ имеются специальные операции разыменования такого указателя, операции.* и ->. Пусть одним из членов структуры AnStruct является указатель pp1на данное типа int.Тогда выражения st1. *pp1 и pst1-> *pp1обеспечат доступ к значению данного, на которое указывает pp1 из st1. Имя массива интерпретируется как указатель-константа на первый элемент массива. Для разыменования указателя, т.е. для доступа к данному по указателю, служит операция *. Если в программе объявлен массив int NewArray [10]; то выражение *NewArray=0присвоит нулевое значение первому элементу массива. Для обращения к произвольному элементу массива указывается индекс элемента, например NewArray [3], что эквивалентно выражению * (NewArray + 3), т.е. сначала увеличивается указатель NewArray на 3 единицы, а затем разыменовыается полученный указатель. При сложении указателя на объект некоторого типа Т с целым числом N значение указателя увеличивается на N, умноженное на длину данного типа Т. Индекс - любое выражение со значением целого типа. Индекс можно задавать не только для имен массивов, но и для любого типа указателя, кроме указателя на тип void: int *pint = & Array[4]; pint [2] =1;
Указатель pintинициализирован адресом 5-го элемента массива Array, а 7-му элементу массива присвоено значение 1. C++ является типизированным языком и в нем определены явные и неявные преобразования типов данных. Неявные преобразования - стандартные арифметические преобразования, выполняемые при двухместных арифметических операциях, а также при операциях присваивания. Некоторые операции в зависимости от своих операндов вызывают преобразование значения операнда одного типа в другой. Если результат является ссылкой, то в результате преобразования получается адрес. Стандартные преобразования для целочисленных осуществляются при необходимости обработки целого. Для этого можно использовать char, short int, элемент перечисления или битовое поле, причем в знаковом и беззнаковом вариантах. Если intпредставляет все значения исходного типа, значение преобразуется к int, иначе оно преобразуется к unsigned int. Если целое преобразуется к знаковому типу, значение не меняется, при условии, что его можно представить с помощью нового типа, иначе значение определяется реализацией. Для выражений floatприменима арифметика с обычной точностью. Если значение с плавающей точкой меньшей точности преобразуется в floatбольшей точности, то изменения значения не происходит. Если же значение с плавающей точкой большей точности преобразуется в floatменьшей точности и значение находится в пределах, задаваемых представлением типа, в результате получается ближайшее большее (меньшее) представимое значение. Если результат оказался вне границ представления типа, поведение его не определено. Преобразование значения с плавающей точкой к целочисленному типу сводится к отбрасыванию дробной части. Важно помнить в какую сторону будет проходить усечение для отрицательных чисел, на разных компиляторах оно определяется по-разному. Результат считается неопределенным, если значение нельзя представить в целочисленном типе. При преобразовании целочисленных значений к значениям с плавающей точкой может произойти потеря точности, если целочисленное значение нельзя точно представить как значение с плавающей точкой. Для большинства операций преобразования операндов тип результата определяется одними и теми же правилами: · если один из операндов - long double, другой операнд также преобразуется в long double; · если один из операндов - double, другой операнд также преобразуется в double; · если один из операндов - float, другой операнд преобразуется в float. Стандартные целочисленные преобразования могут происходить над обоими операндами. Если один из операндов unsigned long, другой операнд преобразуется в unsigned long; · если один из операндов long int, а другой - unsigned int, то при условии, что long int представляет все возможные значения unsigned int, последний преобразуется в long int, в противном случае оба операнда преобразуются в unsigned long int; · если один из операндов - long, другой операнд также преобразуется в long; · если один из операндов - unsigned, другой операнд преобразуется в unsigned. При инициализации, сравнении или использовании указателей иным образом происходят следующие преобразования: · константное выражение, которое сводится к нулю, преобразуется в указатель, обычно называемый пустым указателем. Значение такого указателя будет отлично от любого указателя на объект или функцию; · указатель на функцию можно преобразовать в void* при условии, что для void* отводится достаточно памяти, чтобы хранить этот указатель; · указатель на класс можно преобразовать в указатель на доступный базовый класс данного класса, если такое преобразование не содержит двусмысленность. · указатель на объект любого типа, не являющегося const или volatile, можно преобразовать в void*; Базовый класс считается доступным, если доступны его общие члены; результатом преобразования будет указатель на объект типа базового класса; · пустой указатель (0) преобразуется сам в себя; · выражение типа " массив А" может преобразовываться в указатель на начальный элемент массива; · выражение типа " функция, возвращающая А", преобразуется в " указатель на функцию, возвращающую А", за исключением тех случаев, когда оно используется как операнд адресной операции & или операции вызова функции (). Всюду, где ссылки инициализируются (включая передачу параметров и возврат значения функции) или используются иным образом, ссылка на класс может быть преобразована в ссылку на доступный базовый класс данного класса, если такое преобразование не содержит двусмысленности. Результатом этого будет ссылка на объект базового класса, вложенный в объект производного класса. Явное преобразование типов задается в двух формах. Первая форма совместима с языком С, в ней за именем типа в круглых скобках записывается преобразуемое значение, которое может быть первичным выражением или выражением с одноместной операцией. Имя типа в этом случае может быть представлено последовательностью описателей, например (long int *) р определяет преобразование некоторого данного р в тип указателя на long int. Вторая форма преобразования типа записывается как вызов функции, при этом имя типа должно задаваться идентификатором, например int (x). Результат явного преобразования не является L - значением. Операции увеличения и уменьшения(++ и -) являются префиксными и постфиксными, вызывая увеличение (уменьшение) своего операнда на единицу, т.е. выражение ++хr эквивалентно xr = хr + 1, а - хr эквивалентно хr = хr - 1. Префиксная операция выполняется до того, как ее операнд будет использован в вычислении выражения, а постфиксная операция выполняется после того, как ее операнд будет использован в выражении, например, в результате вычисления выражения ++ xr * 2 + yr-- *3; переменная xr увеличивается на 1, затем умножается на 2; переменная уrумножается на 3, затем уменьшается на 1. Если перед вычислением этого выражения хr и уrбыли равны 1, то результат выражения равен 5; кроме того, переменная получит значение 2, а переменная уr - значение 0. Таким образом, операции увеличения и уменьшения всегда дают побочный эффект, изменяя значения своих операндов. Операнды этих операций должны быть L-значениями. Операция ~ (тильда) применяется только к целому значению и заменяет все биты своего операнда со значением 0 на 1, а биты со значением 1 на 0. Логическое отрицание (операция!) возвращает значение 0 целого типа, если операнд не равен нулю, или значение 1, если операнд равен нулю. Операции " одноместный +" и " одноместный -" имеют обычный математический смысл, знак " +" не изменяет значения операнда, " -" - меняет знак операнда на противоположный. Для получения адреса операнда, являющегося L - значением, применяется операция & (амперсанд). Результатом этой операции будет указатель на соответствующий тип данного. Разыменование указателя, т.е. получение значения данного по указателю на него обеспечивается операцией *. Результат операции разыменования является L-значением. Операции с динамической памятью - операции размещения данных в динамической памяти и удаления динамических данных из памяти. New – операция, требующая в качестве операнда имя типа и предназначенная для размещения данного указанного типа в динамической памяти. Результатом операции является указатель на данное. При невозможности выделить память операция new возвращает значение null ( предопределенная константа, имеющая нулевое значение во всех компиляторах). Память, выделяемую операцией new, можно инициализировать, указав за именем типа скалярного данного начальное значение в круглых скобках. Примеры применения операции new: int * intptr1 = new int; / * создание объекта //типа int и получение указателя на него int *intptr2 - new int(2); //то же с // установкой начального значения 2 inr *intArray - new int [10]; // массив из //10 элементов типа int Delete – операция, освобождающая динамическую память, но не изменяющая значение указателя-операнда. После освобождения памяти использовать этот указатель для обращения к данному нельзя. Данное, размещенное в динамической памяти операцией new, удаляется из памяти delete с операндом-указателем, значение которого получено операцией new, например: delete intArray; delete ip2; Sizeof - операция получения размера данного или типа данного в байтах. Операнд может быть любого типа, кроме функции и битового поля. Если операндом является имя типа, оно должно заключаться в скобки. Возвращаемое значение имеет предопределенный тип size_t; это целый тип, размер которого определяется реализацией компилятора (обычно size_t соответствует unsigned int ). Размер массива равен числубайт, занимаемых массивом в памяти; размер строкового литерала - это число знаков в литерале +1, т.е. завершающий нулевой байт, учитывающийся при определении длины литерала. Значение, возвращаемое sizeof, является константой. Двухместные арифметические операции умножения (*), деления (/), получения остатка от деления нацело (%), сложения (+) и вычитания (-) имеют обычный смысл и обычный относительный приоритет. Если операнды арифметической операции имеют разные типы, то предварительно выполняются стандартные арифметические преобразования и тип результата операции определяется общим типом операндов после стандартных преобразований. Следовательно, выражение 7/2 будет иметь значение 3 типа int, т.к. оба операнда имеют тип int, а выражение 7.0/2 даст результат 3.5 типа double, поскольку этот тип имеет первый операнд. Отношение двух выражений (<, < =, >, > =) – операция, требующая операндов арифметического типа или, чтобы операнды были указателями на одинаковый тип. В случае операндов арифметического типа вычисляются значения операндов, выполняются стандартные арифметические преобразования и возвращается 1 типа int, если отношение выполняется (истинно) или 0, если отношение не выполняется (ложно). При сравнении указателей результат зависит от относительного размещения в памяти объектов, на которые ссылаются указатели. Операции сравнения (== и! =) выполняются подобным образом, но имеют меньший приоритет. Выражения отношений соединяются логическими связками & & и | |. Конъюнкция, логическое умножение (& &) - операция, дающая результат, равный 1 типа int, если оба операнда имеют ненулевые значения. Дизъюнкция, логическое сложение (| |) - операция дает результат, равный 0, если значения обоих операндов нулевые. В общем случае операндами логических связок могут быть любые скалярные значения. Применяется сокращенная форма вычисления значения логических связок: если в операции & & первый операнд равен нулю, то второй операнд не вычисляется и возвращается 0; если в операции | | первый операнд не равен нулю, то второй операнд не вычисляется и возвращается значение 1. Присваивание (=) – операция, возвращающая значение, присвоенное левому операнду. Операция присваивания вычисляется справа налево, т.е. сначала вычисляется присваиваемое значение, затем выполняется присваивание. Это позволяет записывать выражения вида х=у=z=1 для установки одинаковых значений нескольким переменным. Строятся выражения и с побочным эффектом вида (х = 2)*(у = 3)+(z - 4). Результатом этого выражения будет 24, но одновременно переменные х, у и z получат новые значения. Кроме простого присваивания имеется набор составных операций присваивания, в которых присваивание совмещается с указанной двуместной операцией. Запись х += у эквивалентна выражению х = х + у. Для целых операндов определены операции сдвига влево и вправо. При выполнении операции r1«r2 биты первого операнда сдвигаются влево на r1 разрядов и результат имеет тип первого операнда. Освобождающиеся правые разряды заполняются нулями. В свою очередь, при сдвиге вправо (r1> > r2), если r1 имеет тип unsigned, освобождающиеся левые разряды заполняются нулями, а при r1 типа signedв освобождающихся левых разрядах повторяется знаковый разряд. Над целыми операндами допустимы операции поразрядного логического умножения, логического сложения и исключающего или (отрицания равнозначности). В этих операциях операнды рассматриваются как последовательности битов, и операция выполняется над каждой парой соответствующих разрядов из обоих операндов. Конструкция, называемая условным выражением, строится по схеме: условие? выражение1: выражение2. В качестве условия может выступать любое скалярное выражение. Если результат вычисления условия ненулевой, то значением всего выражения будет выражение1, при нулевом значении условия значение всего выражения определяется выражением2. Второй и третий операнды условного выражения должны быть оба: арифметического типа либо однотипными структурами или объединениями, либо указателями одинакового типа, либо один из них - указатель на какой-либо тип, а другой операнд - null или имеет тип void*. Выражение х > 0? 1: 0 возвращает 1, если хбольше 0, и 0 в противном случае Последовательность выражений обычно разделяется запятыми, тогда все выражения вычисляются слева направо, значение последнего выражения в списке, возвращается. Например, в результате вычисления выражения x = 2, е* 3, х + 1; будет получено значение 3 и попутно х получит значение 2. Результат умножения е*3 никак не может быть использован.
4.4. Операторы C++
Операторы - это синтаксические конструкции, которые определяют действия, выполняемые программой. Типы операторов: операторы-выражения, операторы выбора, операторы цикла и оператор перехода. Синтаксис операторов содержит выражения, играющие роль условий, в зависимости от выполнения которых выбирается та или иная последовательность действий. Поскольку в Си нет булевых выражений, в качестве условий используются выражения, дающие скалярные значения. Условие считается выполненным, если это значение отлично от нуля, и невыполненным, если оно равно нулю. Несколько операторов могут быть объединены в составной оператор заключением их в фигурные скобки. Признаком конца оператора (кроме составного) служит точка с запятой, являющаяся в этом случае частью оператора. Перед любым оператором может быть записана метка в виде идентификатора, отделенного от помечаемого оператора двоеточием. Метка служит только для указания ее в операторе перехода. Oпepaтop-выражение - наиболее простой оператор, представляющий собой полное выражение, заканчивающееся точкой с запятой, например: х = 3; у = (х + 1) * t; i++; Выражение, оформленное как оператор, вычисляется, но его значение теряется, и действие оператора-выражения состоит в побочных эффектах, сопровождающих вычисление (например, при выполнении операций присваивания, увеличения или уменьшения). Операторы выбора в C++ представлены условным оператором и переключателем. Условный оператор подобен условным операторам других языков программирования и может использоваться в сокращенной и полной формах, которым соответствуют схемы: if (выражение-условие) оператор if (выражение-условие) оператор1 else оператор2 В сокращенной форме условного оператора вычисляется выражение-условие и, если его значение отлично от нуля, выполняется следующий за условием оператор, в противном случае не производится никаких действий. В полной форме условного оператора при ненулевом значении выражения-условия выполняется оператор1с последующим переходом к следующему оператору программы, а при нулевом значении выражения условия выполняется оператор2 с переходом к следующему оператору программы. Переключатель позволяет выбрать одну из нескольких возможных ветвей вычислений и строится по схеме: switch (целое выражение) оператор Оператор в этом случае представляет собой тело переключателя, всегда является составным и имеет такой вид: { case константа1: операторы case константа2: операторы default: операторы } Выполнение переключателя состоит в вычислении управляющего выражения и переходе к группе операторов, помеченных case-меткой, равной управляющему выражению; если такой case-Метки нет, выполняются операторы по метке default.Пункт defaultможет отсутствовать, и тогда, если управляющему выражению не соответствует ни одна case-метка, весь переключатель эквивалентен пустому оператору. Следует учитывать, что при выполнении переключателя происходит переход на оператор с выбранной case-меткой, и дальше операторы выполняются в естественном порядке. Например, в переключателе switch (count) { case 1: xr = 1; case 2: xr = 2; case 3: xr = 3; default: xr = 4; } если значение countравно 1, то после перехода на case 1будут выполнены все операторы, в результате хr станет равным 4. Чтобы разделить ветви переключателя, в конце каждой ветви нужно записать оператор break.По этому оператору происходит выход из переключателя к следующему оператору программы: switch (count) { case 1: х = 1; break; case 2: x = 2; break; case 3: x = 3; break; default: x =4; } Теперь в зависимости от значения count будет выполняться только одна ветвь переключателя и хr будет принимать одно из четырех предусмотренных значений. Операторы цикла: цикл с предусловием, цикл с пост- условием и цикл с параметром. Цикл с предусловием строится по схеме: while (выражение-условие) оператор При каждом повторении цикла вычисляется выражение-условиеи, если значение этого выражения не равно нулю, выполняется оператор - тело цикла. Цикл с постусловием строится по схеме: do оператор while (выражение-условие) Выражение-условие вычисляется и проверяется после каждого повторения оператора - тела цикла, цикл повторяется, пока условие выполняется. Тело цикла в цикле с постусловием выполняется хотя бы один раз. Цикл с параметром строится по схеме: for (k1; k2; k3) оператор где k1, k2 и k3 - выражения скалярного типа. Цикл с параметром реализуется следующим образом: 1) вычисляется выражение k1, обычно это выражение выполняет подготовку к началу цикла; 2) вычисляется выражение k2, и, если оно равно нулю, выполняется переход к следующему оператору программы (выход из цикла), если к2 не равно нулю, выполняется шаг 3; 3) выполняется оператор - тело цикла; 4) вычисляется выражение k3 - выполняется подготовка к повторению цикла, после чего снова выполняется шаг 2. Рассмотрим следующий пример. Пусть требуется подсчитать сумму элементов некоторого массива из n элементов. При использовании цикла с предусловием это делается так: int s = 0; int i = 0; while (i < n) s += a [i ++]; Эта же задача с применением цикла с постусловием решается следующими операторами: int s = 0; int i = 0; do s += a [i++]; while (i < n); В данном случае повторениями цикла управляет параметр i. Эта задача решается с помощью цикла третьего типа int i, s; for (s = 0, i = 0; i < n; i++) s += a [i]; Объявления переменных можно внести в выражение k1 оператора forи все записать в виде одного оператора for с пустым телом цикла: for (int i = 0, s = 0; i < n; s += a[i++]); Break - оператор для прерывания повторений оператора цикла любого типа в теле цикла. Continue - оператор для перехода к следующему повторению из любого места тела цикла. Goto метка - оператор перехода, дополняющий набор операторов для структурного программирования. Метказадает адрес перехода и помечает оператор в составном операторе, которому принадлежит goto. Вход в составной оператор, содержащий объявления данных, не через его начало запрещен.
|