C++
C++ читать книгу онлайн
С++ – это универсальный язык программирования, задуманный так, чтобы сделать программирование более приятным для серьезного программиста. За исключением второстепенных деталей С++ является надмножеством языка программирования C. Помимо возможностей, которые дает C, С++ предоставляет гибкие и эффективные средства определения новых типов. Используя определения новых типов, точно отвечающих концепциям приложения, программист может разделять разрабатываемую программу на легко поддающиеся контролю части. Такой метод построения программ часто называют абстракцией данных. Информация о типах содержится в некоторых объектах типов, определенных пользователем. Такие объекты просты и надежны в использовании в тех ситуациях, когда их тип нельзя установить на стадии компиляции. Программирование с применением таких объектов часто называют объектно-ориентированным. При правильном использовании этот метод дает более короткие, проще понимаемые и легче контролируемые программы.
Ключевым понятием С++ является класс. Класс – это тип, определяемый пользователем. Классы обеспечивают сокрытие данных, гарантированную инициализацию данных, неявное преобразование типов для типов, определенных пользователем, динамическое задание типа, контролируемое пользователем управление памятью и механизмы перегрузки операций. С++ предоставляет гораздо лучшие, чем в C, средства выражения модульности программы и проверки типов. В языке есть также усовершенствования, не связанные непосредственно с классами, включающие в себя символические константы, inline-подстановку функций, параметры функции по умолчанию, перегруженные имена функций, операции управления свободной памятью и ссылочный тип. В С++ сохранены возможности языка C по работе с основными объектами аппаратного обеспечения (биты, байты, слова, адреса и т.п.). Это позволяет весьма эффективно реализовывать типы, определяемые пользователем.
С++ и его стандартные библиотеки спроектированы так, чтобы обеспечивать переносимость. Имеющаяся на текущий момент реализация языка будет идти в большинстве систем, поддерживающих C. Из С++ программ можно использовать C библиотеки, и с С++ можно использовать большую часть инструментальных средств, поддерживающих программирование на C.
Эта книга предназначена главным образом для того, чтобы помочь серьезным программистам изучить язык и применять его в нетривиальных проектах. В ней дано полное описание С++, много примеров и еще больше фрагментов программ.
Внимание! Книга может содержать контент только для совершеннолетних. Для несовершеннолетних чтение данного контента СТРОГО ЗАПРЕЩЕНО! Если в книге присутствует наличие пропаганды ЛГБТ и другого, запрещенного контента - просьба написать на почту [email protected] для удаления материала
if (i«=0 !! max«i) // ...
очевидно. Тем не менее, всегда, когда программист сомнвается относительно этих правил, следует употреблять скобки, и некоторые программисты предпочитают немного более длинное и менее элегантное
if ( (i«=0) !! (max«i) ) // ...
При усложнении подвыражений употребление скобок станвится более обычным явлением, но сложные подвыражения являюся источником ошибок, поэтому если вы чувствуете потребность в скобках, попробуйте оборвать выражение и использовать дополнительную переменную. Есть и такие случаи, когда приоритты операций не приводят к «очевидному» результату. Например в
if (i amp;mask == 0) // ...
не происходит применения маски mask к i и последующей проверки результата на ноль. Поскольку == имеет приоритет вше, чем amp;, выражение интерпретируется как i amp;(mask==0). В этом случае скобки оказываются важны:
if ((i amp;mask) == 0) // ...
Но, с другой стороны, то, что следующее выражение не рботает так, как может ожидать наивный пользователь, ничего не значит:
if (0 «= a «= 99) // ...
Оно допустимо, но интерпретируется оно как (0«=a)«=99, где результат первого подвыражения или 0 или 1, но не a (если только a не равно 1). Чтобы проверить, лежит ли a в диапазоне 0...99, можно написать
if (0«=a amp; amp; a«=99) // ...
3.2.2 Порядок вычисления
Порядок вычисления подвыражений в выражении неопределен. Например
int i = 1; v[i] = i++;
может вычисляться или как v[1]=1, или как v[2]=1. При отсутствии ограничений на порядок вычисления выражения может генерироваться более хороший код. Было бы замечательно, если бы компилятор предупреждал о подобных неоднозначностях, но большинство компиляторов этого не делают.
Относительно операций amp; amp; и !! гарантируется, что их левый операнд вычисляется раньше, чем правый. Например, b=(a=2,a=1) присвоит b 3.В #3.3.1приводятся примеры использования amp; amp; и !!. Заметьте, что операция следования , (запятая) логически отличается от запятой, которая используется для разделения параметров в вызове функции. Рассмотрим
f1(v[i],i++); // два параметра f2( (v[i],i++) ) // один параметр
В вызове f1 два параметра, v[i] и i++, и порядок вычиления выражений-параметров неопределен. Зависимость выражения -параметра от порядка вычисления – это очень плохой стиль, а также непереносимо. В вызове f2 один параметр, выражение с запятой, которое эквивалентно i++.
С помощью скобок нельзя задать порядок вычисления. Например, a*(b/c) может вычисляться и как (a*b)/c, поскольку * и / имеют одинаковый приоритет. В тех случаях, когда важен прядок вычисления, можно вводить дополнительную переменную, например, (t=b/c,a*t).
3.2.3 Увеличение и уменьшение*
– * Следовало бы переводить как «инкремент» и «декремент», однако мы следовали терминологии, принятой в переводной литратуре по C, поскольку эти операции унаследованы от C. (прим. перев.)
Операция ++ используется для явного выражения приращения вместо его неявного выражения с помощью комбинации сложения и присваивания. По определению ++lvalue означает lvalue+=1, что в свою очередь означает lvalue=lvalue+1 при условии, что lvalue не вызывает никаких побочных эффектов. Выражение, обозначающее (денотирующее) объект, который должен быть увличен, вычисляется один раз (только). Аналогично, уменьшение выражается операцией –. Операции ++ и – могут применяться и как префиксные, и как постфиксные. Значением ++x является нвое (то есть увеличенное) значение x. Например, y=++x эквивлентно y=(x+=1). Значение x++, напротив, есть старое значение x. Например, y=x++ эквивалентно y=(t=x,x+=1,t), где t – перменная того же типа, что и x.
Операции приращения особенно полезны для увеличения и уменьшения переменных в циклах. Например, оканчивающуюся нлем строку можно копировать так:
inline void cpy(char* p, const char* q) (* while (*p++ = *q++) ; *)
Напомню, что увеличение и уменьшение арифметических указателей, так же как сложение и вычитание указателей, осуществляется в терминах элементов вектора, на которые указывает указатель p++ приводит к тому, что p указывает на следующий элемент. Для указателя p типа T* по определению выполняется следующее:
long(p+1) == long(p)+sizeof(T);
3.2.4 Побитовые логические операции
Побитовые логические операции
amp; ! ^ ~ »» ««
применяются к целым, то есть к объектам типа char, short, int, long и их unsigned аналогам, результаты тоже цлые.
Одно из стандартных применений побитовых логических опраций – реализация маленького множества (вектор битов). В этом случае каждый бит беззнакового целого представляет один член множества, а число членов ограничено числом битов. Бнарная операция amp; интерпретируется как пересечение, ! как объединение, а ^ как разность. Для наименования членов такого множества можно использовать перечисление. Вот маленький прмер, заимствованный из реализации (не пользовательского итерфейса) «stream.h»:
enum state_value (* _good=0, _eof=1, _fail=2, _bad=4 *); // хорошо, конец файла, ошибка, плохо
Определение _good не является необходимым. Я просто хтел, чтобы состояние, когда все в порядке, имело подходящее имя. Состояние потока можно установить заново следующим обрзом:
cout.state = _good;
Например, так можно проверить, не был ли испорчен поток или допущена операционная ошибка:
if (cout.state amp;(_bad!_fail)) // не good
Еще одни скобки необходимы, поскольку amp; имеет более всокий приоритет, чем !.
Функция, достигающая конца ввода, может сообщать об этом так:
cin.state != _eof;
Операция != используется потому, что поток уже может быть испорчен (то есть, state==_bad), поэтому
cin.state = _eof;
очистило бы этот признак. Различие двух потоков можно находить так:
state_value diff = cin.state^cout.state;
В случае типа stream_state (состояние потока) такая раность не очень нужна, но для других похожих типов она оказвается самой полезной. Например, при сравнении вектора бит, представляющего множество прерываний, которые обрабатываются, с другим, представляющим прерывания, ждущие обработки.
Следует заметить, что использование полей (#2.5.1) в действительности является сокращенной записью сдвига и маскрования для извлечения полей бит из слова. Это, конечно, моно сделать и с помощью побитовых логических операций, Например, извлечь средние 16 бит из 32-битового int можно следующим образом:
unsigned short middle(int a) (* return (a»»8) amp;0xffff; *)
Не путайте побитовые логические операции с логическими операциями:
amp; amp; !! !
Последние возвращают 0 или 1, и они главным образом ипользуются для записи проверки в операторах if, while или for (#3.3.1). Например, !0 (не ноль) есть значение 1, тогда как ~ 0 (дополнение нуля) есть набор битов все-единицы, который обычно является значением -1.
3.2.5 Преобразование типа
Бывает необходимо явно преобразовать значение одного тпа в значение другого. Явное преобразование типа дает значние одного типа для данного значения другого типа. Например:
float r = float(1);
перед присваиванием преобразует целое значение 1 к знчению с плавающей точкой 1.0. Результат преобразования типа не является lvalue, поэтому ему нельзя присваивать (если только тип не является ссылочным типом).
Есть два способа записи явного преобразования типа: трдиционная в C запись приведения к типу (double)a и функцинальная запись double(a). Функциональная запись не может прменяться для типов, которые не имеют простого имени. Например, чтобы преобразовать значение к указательному типу надо или использовать запись преобразования типа
char* p = (char*)0777;
или определить новое имя типа:
typedef char* Pchar; char* p = Pchar(0777);
По моему мнению, функциональная запись в нетривиальных случаях предпочтительна. Рассмотрим два эквивалентных примера