Б. Страуструп - Язык программирования С++ (1119446), страница 27
Текст из файла (страница 27)
е. увеличенное на 1) значение x. Например, y=++x эквивалентноy=(x+=1). Напротив, значение x++ равно прежнему значению x. Например, y=x++ эквивалентноy=(t=x,x+=1,t), где t - переменная того же типа, что и x.Напомним, что операции инкремента и декремента указателя эквивалентны сложению 1 с указателемили вычитанию 1 из указателя, причем вычисление происходит в элементах массива, на которыйнастроен указатель.
Так, результатом p++ будет указатель на следующий элемент. Для указателя pтипа T* следующее соотношение верно по определению:long(p+1) == long(p) + sizeof(T);Чаще всего операции инкремента и декремента используются для изменения переменных в цикле.Например, копирование строки, оканчивающейся нулевым символом, задается следующим образом:inline void cpy(char* p, const char* q){while (*p++ = *q++) ;}Язык С++ (подобно С) имеет как сторонников, так и противников именно из-за такого сжатого,использующего сложные выражения стиля программирования.
Операторwhile (*p++ = *q++) ;вероятнее всего, покажется невразумительным для незнакомых с С. Имеет смысл повнимательнеепосмотреть на такие конструкции, поскольку для C и C++ они не является редкостью.Сначала рассмотрим более традиционный способ копирования массива символов:int length = strlen(q)for (int i = 0; i<=length; i++) p[i] = q[i];Это неэффективное решение: строка оканчивается нулем; единственный способ найти ее длину - этопрочитать ее всю до нулевого символа; в результате строка читается и для установления ее длины, идля копирования, то есть дважды. Поэтому попробуем такой вариант:84Бьерн Страуструп.Язык программирования С++for (int i = 0; q[i] !=0 ; i++) p[i] = q[i];p[i] = 0;// запись нулевого символаПоскольку p и q - указатели, можно обойтись без переменной i, используемой для индексации:while (*q !=0) {*p = *q;p++;q++;}*p = 0;// указатель на следующий символ// указатель на следующий символ// запись нулевого символаПоскольку операция постфиксного инкремента позволяет сначала использовать значение, а затем ужеувеличить его, можно переписать цикл так:while (*q != 0) {*p++ = *q++;}*p = 0;// запись нулевого символаОтметим, что результат выраженияпример и так:while ((*p++ = *q++) != 0)*p++ = *q++ равен *q.
Следовательно, можно переписать наш{ }В этом варианте учитывается, что *q равно нулю только тогда, когда *q уже скопировано в *p, поэтомуможно исключить завершающее присваивание нулевого символа. Наконец, можно еще более сократитьзапись этого примера, если учесть, что пустой блок не нужен, а операция "!= 0" избыточна, т.к.результат условного выражения и так всегда сравнивается с нулем. В результате мы приходим кпервоначальному варианту, который вызывал недоумение:while (*p++ = *q++) ;Неужели этот вариант труднее понять, чем приведенные выше? Только неопытным программистам наС++ или С! Будет ли последний вариант наиболее эффективным по затратам времени и памяти? Еслине считать первого варианта с функцией strlen(), то это неочевидно. Какой из вариантов окажетсяэффективнее, определяется как спецификой системы команд, так и возможностями транслятора.Наиболее эффективный алгоритм копирования для вашей машины можно найти в стандартной функциикопирования строк из файла <string.h>:int strcpy(char*, const char*);3.2.4 Поразрядные логические операцииПоразрядные логические операции&|^~>><<применяются к целым, то есть к объектам типа char, short, int, long и к их беззнаковым аналогам.Результат операции также будет целым.Чаще всего поразрядные логические операции используются для работы с небольшим по величинемножеством данных (массивом разрядов).
В этом случае каждый разряд беззнакового целогопредставляет один элемент множества, и число элементов определяется количеством разрядов.Бинарная операция & интерпретируется как пересечение множеств, операция | как объединение, аоперация ^ как разность множеств. С помощью перечисления можно задать имена элементаммножества. Ниже приведен пример, заимствованный из <iostream.h>:class ios {public:enum io_state {goodbit=0, eofbit=1, failbit=2, badbit=4};// ...};85Бьерн Страуструп.Язык программирования С++Состояние потока можно установить следующим присваиванием:cout.state = ios::goodbit;Уточнение именем ios необходимо, потому что определение io_state находится в классе ios, а такжечтобы не возникло коллизий, если пользователь заведет свои имена наподобие goodbit.Проверку на корректность потока и успешное окончание операции можно задать так:if (cout.state&(ios::badbit|ios::failbit))// ошибка в потокеЕще одни скобки необходимы потому, что операция & имеет более высокий приоритет, чем операция"|".Функция, обнаружившая конец входного потока, может сообщать об этом так:cin.state |= ios::eofbit;Операция |= используется потому, что в потоке уже могла быть ошибка (т.е.
state==ios::badbit), иприсваиваниеcin.state =ios::eofbit;могло бы затереть ее признак. Установить отличия в состоянии двух потоков можно следующимспособом:ios::io_state diff = cin.state^cout.state;Для таких типов, как io_state, нахождение различий не слишком полезная операция, но для другихсходных типов она может оказаться весьма полезной. Например, полезно сравнение двух разрядныхмассива, один из которых представляет набор всех возможных обрабатываемых прерываний, а другой набор прерываний, ожидающих обработки.Отметим, что использование полей ($$R.9.6) может служить удобным и более лаконичным способомработы с частями слова, чем сдвиги и маскирование. С частями слова можно работать и с помощьюпоразрядных логических операций.
Например, можно выделить средние 16 разрядов из средины 32разрядного целого:unsigned short middle(int a) { return (a>>8)&0xffff; }Только не путайте поразрядные логические операции с просто логическими операциями:&&||!Результатом последних может быть 0 или 1, и они в основном используются в условных выраженияхоператоров if, while или for ($$3.3.1). Например, !0 (не нуль) имеет значение 1, тогда как ~0(дополнение нуля) представляет собой набор разрядов "все единицы", который обычно являетсязначением -1 в дополнительном коде.3.2.5 Преобразование типаИногда бывает необходимо явно преобразовать значение одного типа в значение другого. Результатомявного преобразования будет значение указанного типа, полученное из значения другого типа.Например:float r = float(1);Здесь перед присваиванием целое значение 1 преобразуется в значение с плавающей точкой 1.0f.Результат преобразования типа не является адресом, поэтому ему присваивать нельзя (если толькотип не является ссылкой).Существуют два вида записи явного преобразования типа: традиционная запись, как операцияприведения в С, например, (double)a и функциональная запись, например, double(a).
Функциональнуюзапись нельзя использовать для типов, которые не имеют простого имени. Например, чтобыпреобразовать значение в тип указателя, надо или использовать приведениеchar* p = (char*)0777;86Бьерн Страуструп.Язык программирования С++или определить новое имя типа:typedef char* Pchar;char* p = Pchar(0777);По мнению автора, функциональная запись в нетривиальных случаях предпочтительнее. Рассмотримдва эквивалентных примера:Pname n2 = Pbase(n1->tp)->b_name; // функциональная записьPname n3 = ((Pbase)n2->tp)->b_name;// запись с приведениемПоскольку операция -> имеет больший приоритет, чем операция приведения, последнее выражениевыполняется так:((Pbase)(n2->tp))->b_nameИспользуя явное преобразование в тип указателя можно выдать данный объект за объектпроизвольного типа.
Например, присваиваниеany_type* p = (any_type*)&some_object;позволит обращаться к некоторому объекту (some_object) через указатель p как к объектупроизвольного типа (any_type). Тем не менее, если some_object в действительности имеет тип неany_type, могут получиться странные и нежелательные результаты.Если преобразование типа не является необходимым, его вообще следует избегать. Программы, вкоторых есть такие преобразования, обычно труднее понимать, чем программы, их не имеющие. В тоже время программы с явно заданными преобразованиями типа понятнее, чем программы, которыеобходятся без таких преобразований, потому что не вводят типов для представления понятий болеевысокого уровня. Так, например, поступают программы, управляющие регистром устройства с помощьюсдвига и маскирования целых, вместо того, чтобы определить подходящую структуру (struct) и работатьнепосредственно с ней (см.
$$2.6.1). Корректность явного преобразования типа часто существеннозависит от того, насколько программист понимает, как язык работает с объектами различных типов, икакова специфика данной реализации языка. Приведем пример:int i = 1;char* pc = "asdf";int* pi = &i;i = (int)pc;pc = (char*)i;//////pi = (int*)pc;pc = (char*)pi; //////осторожно: значение pc может измениться.На некоторых машинах sizeof(int)меньше, чем sizeof(char*)осторожно: pc может изменитьсяНа некоторых машинах char* имеет не такоепредставление, как int*Для многих машин эти присваивания ничем не грозят, но для некоторых результат может бытьплачевным.