Лекция 5. Лекция использование основных операций и выражений языка сС. Стандартные функции и директивы препроцессора операции и выражения СС Классификация операций
Скачать 392.63 Kb.
|
59 Лекция 5. ИСПОЛЬЗОВАНИЕ ОСНОВНЫХ ОПЕРАЦИЙ И ВЫРАЖЕНИЙ ЯЗЫКА С/С++. СТАНДАРТНЫЕ ФУНКЦИИ И ДИРЕКТИВЫ ПРЕПРОЦЕССОРА 5.1. Операции и выражения С/С++ Классификация операций Операции бывают унарные (воздействуют на одно значение или выражение), бинарные (участвуют два выражения) и тернарные (три выражения). Основные унарные операции приведены в табл. 5.1. Унарные операции выполняются справа налево. Таблица 5.1 Унарные операции Операции Назначение & Получение адреса операнда * Обращение по адресу (разыменование) – Унарный минус, меняет знак арифметического операнда Поразрядное инвертирование внутреннего двоичного кода (побито- вое отрицание) ! Логическое отрицание (НЕ). В качестве логических значений ис- пользуется 0 – ложь и не 0 – истина, отрицанием 0 будет 1, отрица- нием любого ненулевого числа будет 0 ++ Инкремент или увеличение на единицу: префиксная операция – уве- личивает операнд до его использования, постфиксная операция уве- личивает операнд после его использования. -- Декремент или уменьшение на единицу: префиксная операция – уменьшает операнд до его использования, постфиксная операция уменьшает операнд после его использования sizeof Вычисление размера (в байтах) для объекта того типа, который имеет операнд Основные бинарные операции приведены в табл. 5.2, они выпол- няются слева направо. Таблица 5.2 Бинарные операции Операции Назначение + Бинарный плюс (сложение арифметических операндов) – Бинарный минус (вычитание арифметических операндов) Аддитивные 60 Окончание табл. 5.2 Операции Назначение * Умножение операндов арифметического типа / Деление операндов арифметического типа (ес- ли операнды целочисленные, то выполняется целочисленное деление) % Получение остатка от деления целочисленных операндов (операция по модулю) Мультипликатив- ные << Сдвиг влево битового представления значения левого целочисленного операнда на количество разрядов, равное значению правого операнда >> Сдвиг вправо битового представления значения правого целочисленного операнда на количест- во разрядов, равное значению правого операнда Операции сдвига (определены толь- ко для целочислен- ных операндов) & Поразрядная конъюнкция (И) битовых пред- ставлений значений целочисленных операндов | Поразрядная дизъюнкция (ИЛИ) битовых пред- ставлений значений целочисленных операндов ^ Поразрядное исключающее ИЛИ битовых пред- ставлений значений целочисленных операндов Поразрядные опера- ции < Меньше, чем > Больше, чем <= Меньше или равно >= Больше или равно == Равно != Не равно Операции сравнения && Конъюнкция (И) целочисленных операндов или отношений, целочисленный результат ложь (0) или истина (1) || Дизъюнкция (ИЛИ) целочисленных операндов или отношений, целочисленный результат ложь (0) или истина (1) Логические бинар- ные операции = Присваивание, присвоить значение правого операнда левому += –= *= /= %= |= &= ^= <<= >>= Выполнить соответствующую операцию с ле- вым операндом и правым операндом и присво- ить результат левому операнду Операции присваи- вания и составного присваивания 61 Приоритеты операций приведены в табл. 5.3. Таблица 5.3 Приоритеты операций Ранг Операции 1 ( ) [ ] –> . 2 ! – ++ -- & * (тип) sizeof тип ( ) 3 * / % (мультипликативные бинарные) 4 + - (аддитивные бинарные) 5 << >> (поразрядного сдвига) 6 < > <= >= (отношения) 7 == != (отношения) 8 & (поразрядная конъюнкция «И») 9 ^ (поразрядное исключающее «ИЛИ») 10 | (поразрядная дизъюнкция «ИЛИ») 11 && (конъюнкция «И») 12 || (дизъюнкция «ИЛИ») 13 ?: (условная операция) 14 = *= /= %= -= &= ^= |= <<= >>= (операция присваивания) 15 , (операция запятая) В отличие от унарных и бинарных операций в тернарных услов- ных операциях используется три операнда: Выражение1 ? Выражение2 : Выражение3; Первым вычисляется значение выражения 1. Если оно истинно, то вычисляется значение выражения 2, которое становится результа- том. Если при вычислении выражения 1 получится 0, то в качестве ре- зультата берется значение выражения 3. Выражения Комбинация знаков операций и операндов, результатом которой яв- ляется определенное значение, называется выражением . Каждый операнд в выражении может быть выражением. Значение выражения зависит от расположения знаков операций и круглых скобок в выражении, а также от приоритета выполнения операций. Каждое выражение состоит из од- ного или нескольких операндов, символов операций и ограничителей: x<0 ? -x : x ; //вычисляется абсолютное значение x X * 12 + Y val < 3 -9 62 Выражение, после которого стоит точка с запятой – это оператор- выражение. Рассмотрим подробнее некоторые операции и варианты их ис- пользования. Операция присваивания ( = ) рассматривается как выражение, имею- щее значение левого операнда после присваивания. Присваивание может включать несколько операций присваивания, изменяя значения нескольких операндов, например: Недопустимыми являются: присваивание константе, присваива- ние функции и присваивание результату операции Операции инкремента и декремента ( ++, -- ) относятся к унар- ным арифметическим операциям, которые служат соответственно для увеличения или уменьшения значения, хранимого в переменной целого типа. Например, следующие три оператора дадут один и тот же эффект: Операции инкремента и декремента не только изменяют значения переменных, но и возвращают значения. Таким образом, их можно сделать частью более сложного выражения: Имеется постфиксная и префиксная форма операторов инкремента и декремента. В постфиксной форме записи переменная, к которой int i, j, a; float m, y; m *= y; //m = m * y; i += 2; //i = i + 2; m /= y + 1; //m = m / (y + 1); --a; //a = a - 1; j = i++; //j = i; i = i + 1; j = ++i; //i = i + 1; j = i; sum = sum + 1; sum += 1; ++sum; 2 = a + b; //ошибка getch() = a; //ошибка (a + 1) = 2 + b; //ошибка long a; char b; int c, x, y, z; a = b = c; //эквивалентно b = c; a = b; x = i + (y = 3) - (z = 0); //z = 0, y = 3, x = i + y - z; 63 применена операция, увеличивается (или уменьшается) только после того, как ее значение будет использовано в контексте. Типичной ошибкой является попытка использовать в операции инкремента или декремента операнд, отличный от имени простой переменной: Общий вид операций сравнения (отношения): <выражение 1> <знак операции> <выражение 2> Выражениями могут быть любые базовые (скалярные) типы. Зна- чения выражений перед сравнением преобразуются к одному типу. Результат операции сравнения – значение 1, если отношение истинно, или 0 в противном случае (ложно). Операция сравнения может ис- пользоваться в любых арифметических выражениях: или В С/С++ предусмотрены битовые операции для работы с отдельными битами. Эти операции нельзя применять к переменным вещественного типа. Операндами операций над битами могут быть только выражения, приводимые к целому типу. Операции ( , & , | , ^ ) выполняются пораз- рядно над всеми битами операндов (знаковый разряд не выделяется). Общий вид операции инвертирования: <выражение> Остальные операции над битами имеют вид: <выражение 1> <знак операции> <выражение 2> ++(x + 1); //ошибка ( a < b && b < c) //если ОДНОВРЕМЕННО ОБА a < b //и b < c, то истина, иначе ложь if (a == 0 || b > 0) //если ХОТЯ БЫ ОДИН a==0 //или b > 0, то истина, иначе ложь !0 //1 !10 //0 !((x = 1) < 0) //1 0 < x < 100 //ошибка (0 < x) && (x < 100) //верно int b = 5; int c = 10; a = b > c; //Запомнить результат сравнения a=0 a = (b > c)* 2; //a= 0 64 Ниже приведена таблица истинности логических операций & , | и ^ Операнд 1 Операнд 2 & | ^ 0 0 0 0 0 0 1 0 1 1 1 0 0 1 1 1 1 1 1 0 Операция & часто используется для маскирования некоторого множества бит. Операция ! используется для включения (устанавли- вает в единицу те биты, которые были нулями). Необходимо отличать побитовые операции & и ! от логических бинарных операций && и || Арифметические операции задают обычные действия над операн- дами арифметического типа. Если арифметическая операция содержит операнды различных ти- пов, то компилятор выполняет автоматическое преобразование их типов. Часто арифметические операции используются для обработки чи- сел, например: Операции сдвига << и >> осуществляют соответственно сдвиг вправо и влево своего левого операнда на число позиций, задаваемых правым операндом. Операции сдвига выполняются также для всех разрядов с потерей выходящих за границы бит. int n = 12345; int low, i = 6; low = n %10; //младшая цифра числа n n = n / 10; //отбросить младшую цифру числа n if ( n % i == 0)... //определить - n делится нацело на i ? n = n * 10 + i; //добавить цифру i к значению числа n i % j //i - ( i/j ) * j 12 % 6 //0 13 % 6 //1 x = 1; y = 2; x & y //результат 0, т.к. 0001 & 0010=0000 x && y //результат 1, т.к. в операц. сравнения //оба операнда истина 65 Если левостоящее выражение имеет тип unsigned , то при сдвиге вправо освобождающиеся разряды гарантированно заполняются ну- лями (логический сдвиг). Выражения типа signed могут, но не необя- зательно, сдвигаться вправо с копированием знакового разряда (арифметический сдвиг). При сдвиге влево освобождающиеся разряды всегда заполняются нулями. Если правостоящее выражение отрица- тельно либо больше длины левостоящего выражения в битах, то ре- зультат операции сдвига не определен. Операции сдвига вправо на k разрядов можно использовать для деления, а сдвиг влево – для умножения целых чисел на 2 в степени k : Операция sizeof выполняется на этапе компиляции программы и дает константу, которая равна числу байтов, требуемых для хранения в памяти данного объекта. Объектом может быть имя переменной, массива, структуры или просто спецификация типа. Операторы Основной источник операторов в программе – выражения. Любое из них, ограниченное символом ; , превращается в оператор. Запись действий, которые должен выполнить компьютер, состоит из операторов. При выполнении программы операторы выполняются один за другим, за исключением операторов управления, которые могут изменить последовательное выполнение программы. Различают опера- торы объявления переменных, операторы управления и операторы- выражения. Простейшей формой оператора является пустой оператор: ; Он ничего не делает. Однако он может быть полезен в тех случаях, когда синтаксис требует наличие оператора, а он не нужен. Любая последовательность операторов, заключенная в фигурные скобки {} , может выступать в любой синтаксической конструкции как один составной оператор ( блок ): int i; cout << sizeof(int) << sizeof(i); x << 1; //x * 2 x >> 1; //x / 2 x << 3; //x * 8 0x81 << 1 //10000001<<1=00000010=0x02 0x81 >> 1 //10000001>>1=01000000=0x40 66 Он позволяет рассматривать несколько операторов как один. Область видимости имени, описанного в блоке, простирается до конца блока. Операция запятая ( , ) используется при организации строго га- рантированной последовательности вычисления выражений (исполь- зуется там, где по синтаксису допустима только одна операция, и для организации множественных выражений, расположенных внутри круглых скобок). Форма записи: выражение 1, …, выражение N; Выражения вычисляются слева направо. Например: Выражения, разделенные запятой, не должны быть присваиваниями. Ниже приведенный пример не является примером хорошего кода: 5.2. Преобразование типов В операциях могут участвовать операнды различных типов, в этом случае они неявно преобразуются к общему типу в порядке увеличения их объема памяти, необходимого для хранения их значе- ний. Поэтому неявные преобразования всегда идут от «меньших» объектов к «большим». Схема выполнения преобразований операндов арифметических операций: short, char → int → unsigned → long → double float → double { a = b + 2; b++; } int one = 1, two = 2, three = 100, six = 0; six = (++one, ++two, ++three); //one = 2, two = 3, three = 101, six = 101 char X, Y; (X = Y, Y = getch()); //присваивает переменной X значение Y, считывает символ, //вводимый с клавиатуры, и запоминает его в Y int i, j, k, n; m = ( i = 1, j = i++, k = 6, n = i + j + k); //i = 1, j = i = 1, i = 2, k = 6, n = 2 + 1 + 6, m = n = 9 67 Значения типов char и short всегда преобразуются в int ; если лю- бой из операндов имеет тип double , то второй преобразуется в double ; если один из операндов long , то другой преобразуется в long При присваивании значение правой части преобразуется к типу левой, который и является типом результата. При некорректном ис- пользовании операций присваивания могут возникнуть ошибки. В любом выражении преобразование типов может быть осущест- влено явно , в С для этого достаточно перед выражением поставить в скобках идентификатор соответствующего типа: (тип) выражение; В результате значение выражения преобразуется к заданному типу: Эта форма является устаревшей и сохранена в стандарте С++ только для обеспечения обратной совместимости с программами, на- писанными для С и предыдущих версий С++. В С++ явное преобразование типов производится при помощи следующих операторов: static_cast , dynamic_cast , const_cast и reinterpret_cast . Хотя иногда явное преобразование необходимо, оно служит потенциальным источником ошибок. Явное преобразование типов используется для разыменования ука- зателя void* , для того, чтобы избежать стандартного преобразования или выполнить вместо него собственное. Также может использоваться, float a; int i = 6, j = 4; a = (i + j) / 3; // a = 3 a = (float)(i + j) / 3; → // a = 3.333333 int ival; float fval; double dval; ival + fval + dval; //fval и iva1 преобразуются к double перед сложением float x; int i; x = i; i = x; //float преобразуется в int, дробная часть отбрасывается 68 чтобы избежать неоднозначных ситуаций, в которых возможно не- сколько вариантов применения правил преобразования по умолчанию. Синтаксис операции явного преобразования: cast-name< type >( expression) ; Здесь cast-name – одно из ключевых слов static_cast , const_cast , dynamic_cast или reinterpret_cast , а type – тип, к которому приводится выражение expression Так const_cast слу- жит для трансформации константного типа в неконстантный. Любое иное использование const_cast вызывает ошибку компиляции, как и попытка подобного приведения с помощью любого из трех других операторов. С применением static_cast осуществляются те преоб- разования, которые могут быть сделаны неявно, на основе правил по умолчанию: Оператор reinterpret_cast работает с внутренними представ- лениями объектов ( reinterpret – другая интерпретация того же внут- реннего представления), причем правильность этой операции целиком зависит от программиста. Оператор dynamic_cast применяется при идентификации типа во время выполнения ( runtime type identification ). 5.3. Стандартные математические функции В любой программе, кроме операторов и операций, используются средства библиотек, входящих в среду программирования, которые облегчают создание программ. Часть библиотек стандартизована и поставляется с компилятором. В стандартную библиотеку входят функции, макросы, глобальные константы. Это файлы, хранящиеся в папке include Стандартные математические функции Математические функции языка С декларированы в файлах <сmath> и В большинстве приведенных здесь функций аргументы x , y и ре- зультат выполнения имеют тип double . В табл. 5.5 приведены основные математические функции С/C++. const double d = 97.0; char ch = static_cast< char >( d ); 69 Таблица 5.5 Основные математические функции С/C++ Описание содержится в math.h|cmath double ceil(double x); float ceilf(float x); long double ceill(long double x); Функции округления до наименьшего целого, не меньшего, чем аргумент double cos(double x); Возвращает значение косинуса x, где x – это значение в радианах (2 π ради- ан = 3600) double exp(double x); Возвращает значение числа e, возве- денного в степень x double fabs(double x); float fabsf(float x); long double fabsl(long double x); Абсолютное значение числа с пла- вающей точкой double floor(double x); float floorf(float x); long double loorl(long double x); Наибольшее целое значение, но не большее x double fmod(double x, double y); Функция получения остатка от деле- ния (с плавающей точкой) double log(double x); Возвращает натуральный логарифм x double log10(double x); Возвращает десятичный логарифм x double pow(double x, double y); Возвращает значение x в степени y int rand(void); Возвращает псевдослучайное число в диапазоне от нуля до RAND_MAX void srand(unsigned int seed); Устанавливает свой аргумент как основу ( seed ) для новой последова- тельности псевдослучайных целых чисел, возвращаемых функцией rand() double sin(double x); Возвращает значение синуса аргу- мента x, где x указан в радианах double sqrt(double x); Функция вычисления квадратного корня double tan(double x); Возвращает тангенс аргумента x, где x задан в радианах Пример В прил. 4 содержится описание заголовочных файлов и стандарт- ных функций языка С++. 5.4. Директивы препроцессора Директивы препроцессора представляют собой инструкции, записан- ные в исходном тексте программы и предназначенные для выполнения Z = pow(x,10.0) + 3.7 * pow(x,8.0); //Z = x 10 + 3.7·x 8 70 препроцессором языка. Директивы начинаются со специального знака # , помещаемого в первой позиции строки. Директивы позволяют: • описывать макросы, которые уменьшают трудоемкость написания программы и делают текст программы удобочитаемым и выразительным; • включать текст из других текстовых файлов, содержащих про- тотипы библиотечных и разработанных пользователем функций, шаб- лоны структурных переменных и т.д.; • организовывать условную компиляцию, т.е. в зависимости от заданных в командной строке или среде параметров получать раз- личный программный код. Директива препроцессора #include Заголовочные файлы включаются в текст программы с помощью директивы препроцессора #include . Директива применяется для включения копии указанного в директиве файла в то место, где нахо- дится эта директива. Имя файла может быть указано двумя способами: Различие между ними заключается в методе поиска препроцессо- ром включаемого файла. Если имя файла заключено в угловые скобки ( < > ), считается, что используется стандартный заголовочный файл, и компилятор ищет этот файл в предопределенных местах. Двойные ка- вычки означают, что заголовочный файл – пользовательский, и его по- иск начинается с того каталога, где находится исходный текст проекта. Заголовочные файлы содержат объявления и определения (клас- сов, структур, объединений, перечисляемых типов и прототипов функций), общие для различных программных файлов, и поэтому час- то создаются и включаются в файлы программ. Символические константы и макроподстановка Директива препроцессора #define обычно используется для за- мены часто используемых в программе констант, ключевых слов, опе- раторов и выражений осмысленными идентификаторами. Идентификаторы, которые заменяют числовые или текстовые константы либо произвольную последовательность символов, назы- ваются именованными константами. Идентификаторы, которые представляют некоторую последова- тельность действий, заданную операторами или выражениями языка, называются макроопределениями. #include #include "file.h" 71 Формат директивы определяется как: #define идентификатор строка_текста Директива обеспечивает замену встречающегося в тексте про- граммы идентификатора на соответствующую строку текста, в том числе и с параметрами. Например: #define SIZE 100 //символическая константа #define min(a,b) ((a) < (b) ? (a) : (b)) //макрос #define PRN(number) printf(#number " = %d\n", number); //макрос int scale = 25, param = 10; PRN(scale); PRN(param); result = min(44,uplimit); //result = ((44) < (uplimit) ? (44) : (uplimit)); Как и в случае символических констант, идентификатор макроса заменяется на замещающий текст до начала компиляции программы. Макросы без параметров обрабатываются подобно символическим константам. Если макрос имеет параметры, то сначала в замещающий текст подставляются значения параметров, а затем этот расширенный макрос подставляется в текст вместо идентификатора макроса и спи- ска его параметров. Определения символических констант и макросов могут быть ан- нулированы при помощи директивы #undef Она отменяет самое по- следнее определение поименованного макроопределения. В С++ отдается предпочтение использованию именованных пере- менных типа const , а не символических констант. Константные пере- менные являются данными определенного типа и их имена видны от- ладчику. Если используется символическая константа, то после того, как она была заменена на текст, только этот текст будет виден отлад- чику. Недостатком переменных типа const является то, что им требу- ется память в объеме, соответствующем их типу. Для символических констант дополнительной памяти не требуется. #define TRI 3 #define F 5 #undef TRI //TRI теперь не определен #define F 10 //F переопределен как 10 72 Условные директивы Заголовочный файл, который подключается к модулю проекта, также может содержать директивы #include . Поэтому некоторые заго- ловочные файлы могут оказаться включенными несколько раз. Избе- жать этого позволяют условные директивы препроцессора. Например: Условная директива #ifndef проверяет, не было ли значение FILE_H определено ранее ( FILE_H – это константа препроцессора; та- кие константы принято писать заглавными буквами). Препроцессор обрабатывает следующие строки вплоть до директивы #endif . В про- тивном случае он пропускает строки от #ifndef до #endif Другим распространенным примером применения условных ди- ректив препроцессора является включение в текст программы отла- дочной информации. Рассмотрим еще пример: Директива #ifdef сообщает, что если последующий идентифика- тор O определяется препроцессором, то выполняются все последую- щие директивы вплоть до первого появления #else или #endif . Когда в программе есть #else , то программа от #else до #endif будет вы- полняться, если идентификатор не определен. Макрос assert Макрос assert , определенный в заголовочном файле , выполняет проверку значения выражения. Если значение выражения 0 (ложь), то макрос assert выводит сообщение об ошибке и вызывает функцию abort (из библиотеки утилит общего назначения ), которая завершает выполнение программы. Макрос удобно использо- вать при отладке. Например, переменная х в программе не должна при- нимать значение большее, чем 10. В этом случае макрос assert можно #ifdef O #include "o.h" //выполнится, если O определен #define ST 10 #else #include "w.h" //выполнится, если O не определен #define ST 20 #endif #define FILE_H /* содержимое файла file.h */ #ifndef FILE_H #endif 73 использовать для проверки значения х и вывода сообщения об ошибке, если значение х вышло из допустимого диапазона: Если х будет иметь значение, большее, чем 10, то программа вы- даст сообщение об ошибке, содержащее номер строки и имя файла, после чего завершит свою работу. После того, как в тексте программы объявляется символическая константа NDEBUG , все последующие вызовы макроса assert будут игнорироваться. Таким образом, когда все они будут больше не нуж- ны (т.е. когда отладка закончена), в начале программы достаточно до- бавить строку вместо ручного удаления каждого макроса assert 5.5. Практические задания Для каждой из задач составить блок-схему алгоритма. 1. Дано действительное число x . Не пользуясь никакими другими арифметическими операциями, кроме умножения, сложения и вычи- тания, вычислить за минимальное число операций 2 x 4 – 4 x 3 + 2 x – 1. 2. Ввести любой символ и определить его порядковый номер, а также указать предыдущий и последующий символы. 3. Дана длина ребра куба. Найти площадь грани, площадь полной поверхности и объем этого куба. 4. Треугольник задан величинами своих углов и радиусом опи- санной окружности. Найти стороны треугольника. 5. Дано a . Не используя никаких функций и никаких операций, кроме умножения, получить а 8 за три операции; а 10 и а 16 за четыре операции. assert ( х <= 10 ) ; #define NDEBUG; |