C++
C++ читать книгу онлайн
С++ – это универсальный язык программирования, задуманный так, чтобы сделать программирование более приятным для серьезного программиста. За исключением второстепенных деталей С++ является надмножеством языка программирования C. Помимо возможностей, которые дает C, С++ предоставляет гибкие и эффективные средства определения новых типов. Используя определения новых типов, точно отвечающих концепциям приложения, программист может разделять разрабатываемую программу на легко поддающиеся контролю части. Такой метод построения программ часто называют абстракцией данных. Информация о типах содержится в некоторых объектах типов, определенных пользователем. Такие объекты просты и надежны в использовании в тех ситуациях, когда их тип нельзя установить на стадии компиляции. Программирование с применением таких объектов часто называют объектно-ориентированным. При правильном использовании этот метод дает более короткие, проще понимаемые и легче контролируемые программы.
Ключевым понятием С++ является класс. Класс – это тип, определяемый пользователем. Классы обеспечивают сокрытие данных, гарантированную инициализацию данных, неявное преобразование типов для типов, определенных пользователем, динамическое задание типа, контролируемое пользователем управление памятью и механизмы перегрузки операций. С++ предоставляет гораздо лучшие, чем в C, средства выражения модульности программы и проверки типов. В языке есть также усовершенствования, не связанные непосредственно с классами, включающие в себя символические константы, inline-подстановку функций, параметры функции по умолчанию, перегруженные имена функций, операции управления свободной памятью и ссылочный тип. В С++ сохранены возможности языка C по работе с основными объектами аппаратного обеспечения (биты, байты, слова, адреса и т.п.). Это позволяет весьма эффективно реализовывать типы, определяемые пользователем.
С++ и его стандартные библиотеки спроектированы так, чтобы обеспечивать переносимость. Имеющаяся на текущий момент реализация языка будет идти в большинстве систем, поддерживающих C. Из С++ программ можно использовать C библиотеки, и с С++ можно использовать большую часть инструментальных средств, поддерживающих программирование на C.
Эта книга предназначена главным образом для того, чтобы помочь серьезным программистам изучить язык и применять его в нетривиальных проектах. В ней дано полное описание С++, много примеров и еще больше фрагментов программ.
Внимание! Книга может содержать контент только для совершеннолетних. Для несовершеннолетних чтение данного контента СТРОГО ЗАПРЕЩЕНО! Если в книге присутствует наличие пропаганды ЛГБТ и другого, запрещенного контента - просьба написать на почту [email protected] для удаления материала
Однако обычно разумно предполагать, что в char могут храниться целые числа в диапазоне 0..127 (в нем всегда могут храниться символы машинного набора символов), что short и int имеют не менее 16 бит, что int имеет размер, соответствующий целой арифметике, и что long имеет по меньшей мере 24 бита. Предполагать что-либо помимо этого рискованно, и даже эти эмпирические правила применимы не везде. Таблицу характеристик аппаратного обеспечения для некоторых машин можно найти в #с. 2.6.
Беззнаковые (unsigned) целые типы идеально подходят для применений, в которых память рассматривается как массив битов. Использование unsigned вместо int с тем, чтобы получить еще один бит для представления положительных целых, почти никогда не оказывается хорошей идеей. Попытки гарантировать то, что некоторые значения положительны, посредством описания переменных как unsigned, обычно срываются из-за правил неявного преобразования. Например:
unsigned surprise = -1;
допустимо (но компилятор обязательно сделает предупреждение).
2.3.2 Неявное преобразование типа
Основные типы можно свободно сочетать в присваиваниях и выражениях. Везде, где это возможно, значения преобразуются так, чтобы информация не терялась. Точные правила можно найти в #с.6.6.
Существуют случаи, в которых информация может теряться или искажаться. Присваивание значения одного типа переменной другого типа, представление которого содержит меньшее число бит, неизбежно является источником неприятностей. Допустим, например, что следующая часть программы выполняется на машине с двоичным дополнительным представлением целых и 8-битовыми символами:
int i1 = 256+255; char ch = i1 // ch == 255 int i2 = ch; // i2 == ?
В присваивании ch=i1 теряется один бит (самый значимый!), и ch будет содержать двоичный код «все-единицы» (т.е. 8 единиц); при присваивании i2 это никак не может превратится в 511! Но каким же может быть значение i2? На DEC VAX, где char знаковое, ответ будет -1, на AT amp;T 3B-20, где char беззнаковые, ответ будет 255. В С++ нет динамического (т.е. действующего во время исполнения) механизма для разрешения такого рода проблем, а выяснение на стадии компиляции вообще очень сложно, поэтому программист должен быть внимателен.
2.3.3 Производные типы
Другие типы можно выводить из основных типов (и типов, определенных пользователем) посредством операций описания:
* указатель amp; ссылка [] вектор () функция
и механизма определения структур. Например:
int* a; float v[10]; char* p[20]; // вектор из 20 указателей на символ void f(int); struct str (* short length; char* p; *);
Правила построения типов с помощью этих операций подробно объясняются в #с.8.3-4. Основная идея состоит в том, что описание производного типа отражает его использование. Например:
int v[10]; // описывает вектор i = v[3]; // использует элемент вектора
int* p; // описывает указатель i = *p; // использует указываемый объект
Вся сложность понимания записи производных типов проистекает из того, что операции * и amp; префиксные, а операции [] () постфиксные, поэтому для формулировки типов в тех случаях, когда приоритеты операций создают затруднения, надо использовать скобки. Например, поскольку приоритет у [] выше, чем у *, то
int* v[10]; // вектор указателей int (*p)[10]; // указатель на вектор
Большинство людей просто помнят, как выглядят наиболее обычные типы.
Описание каждого имени, вводимого в программе, может оказаться утомительным, особенно если их типы одинаковы. Но можно описывать в одном описании несколько имен. В этом случае описание содержит вместо одного имени список имен, разделенных запятыми. Например, два имени можно описать так:
int x, y; // int x; int y;
При описании производных типов можно указать, что операции применяются только к отдельным именам (а не ко всем остальным именам в этом описании). Например:
int* p, y; // int* p; int y; НЕ int* y; int x, *p; // int x; int* p; int v[10], *p; // int v[10]; int* p;
Мнение автора таково, что подобные конструкции делают программу менее удобочитаемой, и их следует избегать.
2.3.4 Тип void
Тип void (пустой) синтаксически ведет себя как основной тип. Однако использовать его можно только как часть производного типа, объектов типа void не существует. Он используется для того, чтобы указать, что функция не возвращает значения, или как базовый тип для указателей на объекты неизвестного типа.
void f() // f не возвращает значение void* pv; // указатель на объект неизвестного типа
Переменной типа указатель на void (void *), можно присваивать указатель любого типа. На первый взгляд это может показаться не особенно полезным, поскольку void* нельзя разименовать, но именно это ограничение и делает тип void* полезным. Главным образом, он применяется для передачи указателей функциям, которые не позволяют сделать предположение о типе объекта, и для возврата из функций нетипизированных объектов. Чтобы использовать такой объект, необходимо применить явное преобразование типа. Подобные функции обычно находятся на самом нижнем уровне системы, там, где осуществляется работа с основными аппаратными ресурсами. Например:
void* allocate(int size); // выделить void deallocate(void*); // освободить
f() (* int* pi = (int*)allocate(10*sizeof(int)); char* pc = (char*)allocate(10); //... deallocate(pi); deallocate(pc); *)
2.3.5 Указатели
Для большинства типов T T* является типом арифметический указатель на T. То есть, в переменной типа T* может храниться адрес объекта типа T. Для указателей на вектора и указателей на функции вам, к сожалению, придется пользоваться более сложной записью:
int* pi; char** cpp; // указатель на указатель на char int (*vp)[10]; // указатель на вектор из 10 int'ов int (*fp)(char, char*); // указатель на функцию //получающую параметры(char, char*) // и возвращающую int
Основная операция над указателем – разыменование, то есть ссылка на объект, на который указывает указатель. Эта операция также называется косвенным обращением. Операция разыменования – это унарное * (префиксное). Например:
char c1 = 'a'; char* p = amp;c1; // в p хранится адрес c1 char c2 = *p; // c2 = 'a'
Переменная, на которую указывает p,– это c1, а значение, которое хранится в c1, это 'a', поэтому присваиваемое c2 значение *p есть 'a'.
Над указателями можно осуществлять некоторые арифметические действия. Вот, например, функция, подсчитывающая число символов в строке (не считая завершающего 0):
int strlen(char* p) (* int i = 0; while (*p++) i++; return i; *)
Другой способ найти длину состоит в том, чтобы сначала найти конец строки, а затем вычесть адрес начала строки из адреса ее конца:
int strlen(char* p) (* char* q = p; while (*q++) ; return q-p-1; *)
Очень полезными могут оказаться указатели на функции. Они обсуждаются в #4.6.7.
2.3.6 Вектора
Для типа T T[size] является типом «вектор из size элементов типа T». Элементы индексируются (нумеруются) от 0 до size-1. Например:
float v[3]; // вектор из трех float: v[0], v[1], v[2] int a[2][5]; // два вектора из пяти int char* vpc; // вектор из 32 указателей на символ
Цикл для печати целых значений букв нижнего регистра можно было бы написать так:
extern int strlen(char*);
char alpha[] = «abcdefghijklmnoprstuvwxyz»;
main()
(* int sz = strlen(alpha);
for (int i=0; i«sz; i++) (* char ch = alpha[i]; cout „„ "'" „« chr(ch) «« "'" «« " = " «« ch «« « = 0“ «« oct(ch) «« « = 0x“ «« hex(ch) «« «n“; *) *)
Функция chr() возвращает представление небольшого целого в виде строки; например, chr(80) это "P" на машине, на которой используется набор символов ASCII. Функция oct() строит восьмеричное представление своего целого аргумента, а hex() строит шестнадцатеричное представление своего целого аргумента; chr() oct() и hex() описаны в «stream.h». Функция strlen() использовалась для подсчета числа символов в alpha; вместо этого можно было использовать значение размера alpha (#2.4.4). Если применяется набор символов ASCII, то выдача выглядит так: