Значения битов Результат операции Е1 Е2 Е1 & E2 E1 | E2 E1 ^ E2
E1 0 0 0 0 0 1 1 0 0 1 1 0 0 1 0 1 1 1 1 1 1 1 0 0 Глава 6. Дополнительные сведения о типах данных, операциях, выражениях Следует отличать побитовые операции над целыми числами от логических. Например Здесь a = 1 , b = 0 , потому что из a = x && y следует т. к. x≠0 и y≠0 , то по определению операции && результат будет истинен, а в языке С истинный результат имеет значение 1. С другой стороны, для поразрядного "и" получим х = 01 , y = в двоичной системе счисления, в которую надо перевести операнды, чтобы выполнить побитовую операцию & . Тогда получим 01 & 10 = Операции и выражения присваивания Выражения вида можно записывать в виде читается к i добавить 2 ). Это правило распространяется на операции + , - , * , / , % , << , >> , & , | , В листинге 6.3 приведен текст программы, которая подсчитывает число ненулевых битов целого числа и использует операцию сдвига. Листинг 6.3 // 6.1_2011.cpp #include "stdafx.h" #include для getchar(),putchar() #include для _getch() using namespace System; Проверка побитовых логических операций- Функция подсчета количества битов в целом числе- int bitcount(unsigned int n) { int b; for(b=0; n != 0; n>>=1) if(n & 01) //01 — восьмеричная единица b++; return(b); } //------------------------------------------------------------- int main() { int n=017; восьмеричное число (4 единицы) printf("The unit's quantity in n=%d\n",bitcount(n)); _getch(); } //-----------------------------------------------------------
94 Часть I. Изучение языка С/С++Рис. 6.1. Результат работы программы листинга Результат работы этой программы показан на рис. 6.1. Суть алгоритма функции bitcount() состоит в следующем в цикле, который идет попеременной цикла b , целое число n сравнивается с восьмеричной единицей (она же — двоичная единица) с помощью операции & (и. Так как число 01 можно представить и как, например, 00000001 , то учитывая то, как работает операция & , можно сказать, что результатом вычисления выражения (n & 01) будет значение последнего бита числа Действительно если в последнем бите числа n — нуль, то выражение (n & даст тоже нуль если — единица, то (n & 01) даст единицу. Следовательно, выражение (n & 01) "просматривает" все биты числа n . То есть в телеоператора, содержащего это выражение, можно подсчитывать, сколько раз выражение было равно единице (что на самом деле означает, сколько единиц содержит число n ). Просмотр содержимого числа n происходит за счет сдвига его содержимого вправо на один битв каждом цикле. Мы видели, что выражение равносильно выражению n=n >>1 (те. "сдвинуть содержимое переменной n на один разряд вправо и результат записать в n "). Поэтому действие будет происходить так сначала в заголовочной части оператора for вычисляется значение переменной цикла b , которая получает значение нуль. Затем там же, в заголовочной части for , вычисляется выражение, определяющее условие продолжения/завершения цикла (n !=0) . Следовательно, значение переменной, в которой мы накапливаем единицы, должно быть ненулевым. Нулевым оно может стать потому, что после каждого цикла до перехода на новый цикл мы сдвигаем вправо на один разряд содержимое n , что, в конце концов, приведет к тому, что в значении n останутся только нули. Но здесь надо иметь ввиду следующее число, которое хранится в переменной типа int , может иметь знак. Точнее, знак есть всегда, но он может быть отрицательным. При сдвиге вправо освобождающиеся биты будут заполняться содержимым знакового разряда. Если число положительное, то ничего страшного в этом нет — его знаковый разряд содержит нуль, и освобождающиеся при сдвиге вправо биты будут заполнены нулем. Это так называемый арифметический сдвиг числа. Если же число отрицательное, тов его знаковом разряде будет единица, и ею станут заполняться освобождающиеся при сдвиге биты. Вот этого-то нам как разине надо Чтобы избежать такой неприятности, следует сдвигаемое вправо число объявить с атрибутом unsigned (у нас таки объявлена переменная n ). В этом случае освобождающиеся от сдвига вправо разряды станут заполняться не знаковым разрядом, а нулем. Такой сдвиг называют логическим.
Глава 6. Дополнительные сведения о типах данных, операциях, выражениях Условное выражение Это конструкция вида ? e2 : e3 , где ее, е — некоторые выражения. Читается эта конструкция так "Если е отлично от нуля (те. истинно, то значением этой конструкции будет значение выражения е, иначе — е. Пользуясь условным выражением, можно упрощать некоторые операторы. Например, вместо того, чтобы писать if(ab) ? a : Эти выражения, как обычные выражения, можно помещать в качестве аргументов функции printf() и т. д. Операторы и блоки Если за любым выражением (например, x=0 , i++ или printf(...) ) стоит точка с запятой, то такое выражение в языке С называется оператором. Таким образом x=0; — оператор i++; — оператор printf(...); — оператор. Фигурные скобки {} служат для объединения операторов в блоки. Такой блок синтаксически эквивалентен одному оператору, но точка с запятой после блока не ставится (компилятор определяет конец блока по его закрывающей скобке. Исходя из этого определения, можно записать формат задания операторов for , while , if таким образом выражение) выражение) выражение блок блок блок Если в блоке всего один оператор, то фигурные скобки можно опустить. В блоке можно объявлять переменные, но следует помнить, что они будут локальными, те. неизвестными за пределами блока. Конструкция Эта конструкция используется при необходимости сделать выбор. Синтаксис выражение) блок else блок 96 Часть I. Изучение языка С/С++Работает эта конструкция так Вычисляется выражение в скобках оператора Если значение выражения истинно, то выполняется тело if (блок операторов. Если значение выражения ложно, то выполняется тело else (блок операторов. Часть else является необязательной если указанное в if выражение ложно, то управление передается на выполнение следующего за if оператора, те. тело if не выполняется. Как ни покажется странным замечание, но его приходится делать if и его тело — это одно целое Их нельзя разрывать. Это же касается и операторов while , Часть else самостоятельно не применяется. Конструкция Когда в соответствии с реализуемым в программе алгоритмом приходится делать многовариантный выбор, то применяют конструкцию вида выражение блок else выражение блок else выражение блок else выражение блок else блок Работает эта конструкция так Последовательно вычисляется выражение в каждой строке. Если выражение истинно, то выполняется тело (те. блок операторов) и происходит выход из конструкции на выполнение следующего за ней оператора. Если выражение ложно, то начинает вычисляться выражение в следующей строке и т. д. Последняя часть конструкции ( else блок) необязательна. Приведем пример функции поиска заданного элемента в упорядоченном по возрастанию элементов числовом массиве. Пусть даны массив v[n] и число x . Надо определить, принадлежит лих массиву. Так как элементы массива предполагаются упорядоченными по возрастанию их значений, то поиск проведем, применяя метод половинного деления (иначе называемый двоичным поиском. Суть метода такова Рассматривается отрезок, на котором расположены все элементы числового массива Глава 6. Дополнительные сведения о типах данных, операциях, выражениях Если массив имеет размерность n , то отрезок, на котором расположены номера его элементов, это [ 0, n-1 ] , потому что номер первого элемента массива будет 0 , а последнего — Этот отрезок делится пополам. Средняя точка отрезка вычисляется как j = (0 + (n-1))/2 В этой средней точке вычисляется значение v[j] и проверяется значение х больше, меньше или равно v[j] : если х < v[j] , значит, х находится слева от середины отрезка если х > v[j] , то х находится справа от середины отрезка если х = v[j] , значит, х принадлежит массиву. В последнем случае программу надо завершить, либо рассматривать ту из половинок отрезка, в которой, возможно, содержится х (х может и не содержаться в массиве, те. не совпадать ни с одним из элементов массива, затем делить половину отрезка пополам и проверять, как и первый отрезок. Когда же следует остановиться Один вариант останова мы уже видели когда значение х совпадет с одной из середин отрезка. А второй — когда отрезок "сожмется" так, что его нижняя граница (левый край) совпадет с верхней (правый край. Это произойдет обязательно, потому что мы станем делить отрезок пополам ив качестве результата брать целую часть отделения, поскольку, как мы видели, ее надо использовать в качестве индекса массива. А индекс массива это величина обязательно целая, т. к. это порядковый номер элемента массива. Например, массив из трех элементов будет иметь отрезок [0,2] . Делим пополам и получаем (0+2)/2=1 (те. имеем два отрезка [0,1] и [1,2] ). Если станем искать в [0,1] , то придется находить его середину (0+1)/2=0 (мы помним, что операция / при работе с целыми операндами дробную часть результата отбрасывает и оставляет только целую часть, что нами требуется для нахождения индекса массива. Видим, что левый конец отрезка ( 0 ) совпал с правым ( 0 ), т. к. правый конец нового отрезка получился равным нулю. Вот в этот момент деление пополам надо прекратить и завершить программу. Если в нашем случае получится так, что отрезок сжался в точку, а число хне сравнялось ни с одним элементом массива v[] , то надо вывести об этом событии информацию например, -1 . Если обнаружено, что х содержится в массиве v[] , тона- до вывести номер элемента v[] , на котором и произошло совпадение. Значение индекса массива, на котором произошло совпадение, положительно. Если же совпадения не обнаружено, то значение индекса — отрицательно. Это все необходимо для того, чтобы при обращении к функции поиска вхождения проверить результат поиска входит ли число х вмассив v[] или не входит. Текст программы представлен в листинге 6.4.
98 Часть I. Изучение языка С/С++ Листинг 6.4 // 6.2_2011.cpp #include "stdafx.h" #include для getchar(),... #include для _getch() #include для atoi() using namespace System; #define eof -1 //Ctrl+z #define maxline 100 Ввод строки с клавиатуры- int getline(char s[],int lim) { int c,i; for(i=0; i } //----------------------------------------- int binary(int x,int v[], int n) ищет в массиве v[n] элемент со значением "х" n — размерность массива { int low,high,mid; low=0; high=n-1; while(low <= high) { mid=(low+high)/2; if(x < v[mid]) high=mid — 1; else if(x > v[mid]) low=mid + 1; else return(mid); нашли } //while return(-1); не нашли } //------------------------------- int main() { int v[maxline]={0,1,2,3,4,5,6,7,8,9};
Глава 6. Дополнительные сведения о типах данных, операциях, выражениях int c,i,x; char s[maxline]; do { printf("Enter your new >"); getline(s,maxline); x=atoi(s); i=binary(x,v,10); if(i != -1) printf("entrance found \n"); else printf("entrance not found\n"); printf("Continue-Enter, exit-Ctrl+z\n"); } while((c=getchar()) != eof) ; это — конец оператора do...while. Нам требовалось, чтобы тело while выполнилось хотя бы один раз */ } //main() Рассмотрим работу функции В переменных low , high , mid размещаются, соответственно, текущие значения нижней границы отрезка, верхней границы отрезка и его середины. Если значение числа находится в левой половине поделенного отрезка, то изменяется отрезок поиска переменная low остается без изменения, а переменная high сдвигается на середину (поэтому данной переменной присваивается значение середины, в результате чего получается отрезок, являющийся левой половиной предыдущего отрезка. Если значение числа находится в правой половине поделенного отрезка, то изменяется отрезок поиска переменная high остается без изменения, а переменная low сдвигается на середину, в результате чего получается правый отрезок. Если значение совпадает со значением середина отрезка, то функция возвращает переменную mid и процесс поиска прекращается. Если цикл поиска закончился, это сигнал о том, что просмотрены все элементы, и совпадения не найдено. В этом случае будет возвращено отрицательное число. Рассмотрим работу основной программы. Выражение int v[maxline]={0,1,2,3,4,5,6,7,8,9}; — это инициализация (определение элементов) массива. Для простоты проверки работы функции поиска мы задали значения элементов, совпадающими с номерами своих элементов. Далее с помощью функции в строку вводится значение x и переводится с помощью функции atoi() в целое число. Затем происходит обращение к функции binary() и проверяется результат ее работы равно ли возвращенное ею значение –1. Все эти операторы помещены в блок — тело оператора do...while и выполняются в цикле, пока не будет нажата комбинация клавиш +. Такая структура
100 Часть I. Изучение языка С/С++ Рис. 6.2. Результат работы программы листинга дает возможность вводить разные значения переменной x . Результат работы программы приведен на рис. 6.2. Переключатель При большом многовариантном выборе использование комбинации if...else дает довольно запутанную картину. В таких ситуациях удобнее использовать специальный оператор switch — оператор выбора одного из многих вариантов. Его называют также переключателем. Поясним работу оператора на примере программы подсчета количества встречающихся символов a , b , c , d во введенной с клавиатуры строке. Текст программы представлен в листинге 6.5, результат ее работы — на рис. 6.3. Листинг 6.5 // 6.3_2011.cpp #include "stdafx.h" #include для getchar().... #include для _getch() using namespace System; #define eof -1 //Ctrl+z #define m 5 количество счетчиков в операторе switch Функция подсчета символов ---------------------------- /* char c — входной символ, подсчет которого ведется (сколько раз встретится) int v[] — с помощью элементов этого массива организованы счетчики char s[]- сюда помещаются символы, которые подсчитываются (для их последующей распечатки) */ int CountSimb(char c,int v[],char s[])
Глава 6. Дополнительные сведения о типах данных, операциях, выражениях { int i; switch(c) { case 'a': v[0]++; s[0]=c; break; case 'b': s[1]=c; v[1]++; break; case 'c': v[2]++; s[2]=c; break; case 'd': v[3]++; s[3]=c; break; case '\012': чтобы не учитывался символ break; default: все прочие введенные символы попадают в этот блок v[4]++; s[4]='!'; /* признак "прочие символы" (введен для печати чтобы было понятно, что счетчик относится к "прочим) */ break; } return(0); } //------------------------------- int main() { int c,i,a[m]; char s[m]; for(i=0; i < m; i++) a[i]=0; printf("Enter your characters,,, >"); i=0; while((c=getchar()) != eof) { CountSimb(c,a,s); i++; } for(i=0; i < m; i++) printf("Key=%c count =%d\n",s[i],a[i]); _getch(); } //main()
102 Часть I. Изучение языка С/С++ Рис. 6.3. Результат работы программы листинга Использование оператора switch демонстрируется посредством его включения в функцию CountSimb(char c,int v[],char s[]) , параметры которой описаны перед ее определением. У самого оператора switch есть заголовочная часть, заключенная в круглые скобки, и тело — блок операторов. В заголовочной части указано имя переменной, значение которой будет анализироваться оператором, ив зависимости от значения этой переменной, произойдет передача управления в тот или иной участок блока. В заголовочной части оператора может быть расположено не только имя переменной, но и выражение целого типа, но никак не типа float или типа строки символов. Участки блока определяются ключевым словом case (случай, после которого через пробел стоит конкретное значение анализируемой переменной. В нашем случае переменная описана как символ, поэтому конкретное ее значение, определяющее начало участка блока, написано в соответствии с правилами записи символьных констант например, 'd' . Если бы переменная была типа int , то надо было бы писать, например, После конкретного значения выражения в заголовочной части обязательно стоит символ двоеточия, обозначающий начало участка обработки данного случая — это как бы метка начала участка обработки, относящегося к данному случаю. Работа внутри тела оператора switch организована так Анализируется выражение заголовочной части. Управление передается на выполнение того участка тела, значение которого в метке совпадает со значением выражения в заголовочной части. На участке могут находиться обычные операторы. Они должны быть завершены оператором break прервать, который прервет выполнение switch и передаст управление следующему за телом switch оператору. Оператор break прерывает не только выполнение оператора switch , но и while , и for . Если в конце участка не поставить break , то программа перейдет к следующему участку, потом к следующему и т. д. В конце концов, она дойдет до конца тела оператора и выйдет из него.
Глава 6. Дополнительные сведения о типах данных, операциях, выражениях Этим свойством часто пользуются. Например, если надо обработать какие-то значения переменной в заголовочной части, но для символов a и b требуется выполнить общий алгоритм. В этом случаев теле switch можно записать { case 'a': case 'b': операторы Какой бы из символов — а или b — не поступил на вход переключателя, все равно будут выполняться одни и те же операторы. Нов теле switch мы видим еще одно ключевое слово default . Это "метка" участка "прочие все значения заголовочного выражения, которые отличаются от значений, указанных в переменной case , будут приводить на этот участок, где можно располагать свои операторы, в том числе В основной программе сначала инициализируется массив a , в элементах которого будут накапливаться данные по встреченным символам. Затем запрашивается строка символов, а после организуется обработка каждого символа в цикле и передача его в функцию, в которой находится оператор В эту функцию также передается имя массива, в элементах которого установлены счетчики количества символов (мaссив a ), и имя массива s , куда будут записываться сами символы, чтобы потом можно было их вывести на экран. Все прочие символы, не попадающие в предложение case , отмечаются в массиве s символом Когда встретится символ +, цикл обработки символов завершится, и произойдет вывод счетчиков из массива Уточнение по работе оператора Мы знаем, что заголовочная часть этого оператора содержит три выражения. Оказывается, что любое из них может быть опущено (и даже все, нос одним условием точки с запятой должны остаться на своих местах. Это удобно для организации бесконечного цикла, выход из которого можно осуществить, проверяя в теле некоторые условия и пользуясь при этом оператором Кроме того, в теле for могут находиться другие операторы Оператор Этот оператор родственен оператору break : он используется не для выхода из цикла, а для продолжения цикла (возврата на реинициализацию), не доходя до конца оператора цикла ( while , for ). Оператором continue удобно пользоваться в тех случаях, когда при выполнении тела цикла ясно, что не следует продолжать выполнение операторов дальше, а надо возвращаться на новый виток цикла. Например,
104 Часть I. Изучение языка С/С++ имеем массив целых чисел int Требуется выбрать из него только положительные числа и обработать их. Такой цикл можно построить следующим образом for(int i=0; i < n; i++) { if(A[i] <= 0) continue; другие операторы То есть сразу проверяем если число отрицательное или нулевое, то его не требуется рассматривать, а можно переходить к проверке следующего. Оператор continue передаст управление на реинициализацию цикла (на выражение i++ в заголовочной части for ). Оператор и метки Это оператор безусловного перехода на участок программы, который помечен меткой набором символов, оканчивающимся двоеточием и начинающимся с буквы. Структурное программирование своим появлением на свет во многом обязано этому оператору, который позволял делать такие петли в программе, что и самому автору трудно было в них разобраться. Этот оператор может прервать цикли выйти из него наметку. Например, можно написать main() { for(;;) {goto v1;} v1: printf("Hello\n"); Увлекаться этим оператором нежелательно. В крайнем случае, старайтесь передавать управление только вперед.
ГЛАВА Работа с указателями
|