Вы находитесь на странице: 1из 5

1 Понятие объекта. Класс, тип и объект в языке С++.

Статические объекты могут быть локальными в блоке или располагаться вне блоков, но в обоих случаях их значения
Типы сохраняются после выхода из блока (или функции) до повторного в него входа. Внутри блока (в том числе и в блоке,
Основные типы, наиболее непосредственно отвечающие средствам аппаратного обеспечения, такие: char short int образующем тело функции) статические объекты в объявлениях помечаются словом static. Объекты, объявляемые вне
long float double всех блоков на одном уровне с определениями функций, всегда статические. С помощью ключевого слова static их
Первые четыре типа используются для представления целых, последние два - для представления чисел с плавающей
можно сделать локальными в пределах транслируемой единицы (в этом случае они получают атрибут внутренней связи), 2 Контроль доступа к объекту.
точкой. Переменная типа char имеет размер, естественный для хранения символа на данной машине (обычно, байт),
а переменная типа int имеет размер, соответствующий целой арифметике на данной машине (обычно, слово). и они становятся глобальными для всей программы, если опустить явное указание класса памяти или использовать При изучении определений базовых классов можно встретить элементы, объявленные как public, private и protected
Диапазон целых чисел, которые могут быть представлены типом, зависит от его размера. В C++ размеры измеряются ключевое слово extern (в этом случае они получают атрибут внешней связи). (общие, частные и защищенные). Производный класс может обращаться к общим элементам базового класса, как будто
в единицах размера данных типа char, поэтому char по определению имеет размер единица. При запуске программы , включающей классы iostream ,создаются и инициируются 4 объекта: они определены в производном классе. С другой стороны, производный класс не может обращаться к частным
Соотношение между основными типами можно записать так: элементам базового класса напрямую. Вместо этого для обращения к таким элементам производный класс должен
1 = sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long)
sizeof(float) <= sizeof(double)
• Cin- обрабатывает ввод с клавиатуры. Объект cin включает перегруженный оператор ввода (»), который использовать интерфейсные функции. Защищенные элементы базового класса занимают промежуточное положение
между частными и общими. Если элемент является защищенным, объекты производного класса могут обращаться к
В целом, предполагать что-либо еще относительно основных типов неразумно. В частности, то, что целое записывает данные, хранимые в буфере cin, в локальную переменную someVariable. Причем оператор нему, как будто он является общим. Для оставшейся части вашей программы защищенные элементы являются как бы
достаточно для хранения указателя, верно не для всех машин. К основному типу можно применять прилагательное ввода перегружен таким образом, что подходит для ввода данных всех базовых типов, включая int&, частными. Единственный способ, с помощью которого ваши программы могут обращаться к защищенным элементам,
const. Это дает тип, имеющий те же свойства, что и исходный тип, за исключением того, что значение переменных shorts, long&, doubles, floats, char&, char* и т.п. Когда компилятор встречает выражение cin » someVariable, состоит в использовании интерфейсных функций. Следующее определение класса book использует метку protected,
типа const не может изменяться после инициалиизации. то вызывается вариант оператора ввода, соответствуюший типу переменной someVariable. чтобы позволить классам, производным от класса book, обращаться к элементам title, author и pages напрямую,
const float pi = 3.14; используя оператор точку:
const char plus = `+`;
Символ, заключенный в одинарные кавычки, является символьной константой. Заметьте, что часто константа,
• Cout- вывод сообщений на экран.Объект позволяет форматировать данные,выравнивать столбцы и
class book
{
определенная таким образом, не занимает память; просто там, где требуется, ее значение может использоваться выводить числовые значения в десятичном и 16-теричном формате. public:
непосредственно. Константа должна book(char *, char *, int) ;
инициализироваться при описании. Для переменных инициализация необязательна, но настоятельно
рекомендуется. Оснований для введения локальнной переменной без ее инициализации очень немного. К любой
• Cerr - обрабатывает не буфериэированный вывод ошибок на стандартное устройство вывода
void show_book(void) ;
protected:
комбинации этих типов могут применяться арифметические операции: сообшений об ошибках, т.е. на экран. Поскольку вывод не буфериэированный, то все данные, char title [64];
+ (плюс, унарный и бинарный) направляемые в сеrr, сразу же выводятся устройством вывода. char author[64];
- (минус, унарный и бинарный) int pages;
* (умножение)
/ (деление)
• Clog - обрабатывает буферизированные сообщения об ошибках, которые выводятся на стандартное уст- };
Если вы предполагаете, что через некоторое время вам придется породить новые классы из создаваемого сейчас класса,
А также операции сравнения: ройство вывода сообшений об ошибках (экран). Зачастую эти сообщения переадресуются в файл установите, должны ли будущие производные классы напрямую обращаться к определенным элементам создаваемого
== (равно) регистрации класса, и объявите такие элементы защищенными, а не частными.
!= (не равно) Класс Защищенные элементы обеспечивают доступ и защиту
< (меньше) Класс в C++ — нечто большее, чем простая структура данных. Это аналог модуля из других языков Как вы уже знаете, программа не может обратиться напрямую к частным элементам класса. Для обращения к частным
> (больше) программирования, средство упорядочения символьных имен. элементам программа должна использовать интерфейсные функции, которые управляют доступом к этим элементам.
<= (меньше или равно) Новый тип создается путем объявления класса. Класс— это просто коллекция переменных (причем часто различных Как вы, вероятно, заметили, наследование упрощает программирование в том случае, если производные классы могут
>= (больше или равно) типов), скомбинированная с набором связанных функций обращаться к элементам базового класса с помощью оператора точки. В таких случаях ваши программы могут
Заметьте, что целое деление дает целый результат: 7/2 есть 3. Над целыми может выполняться операция % получения Автомобиль можно представлять себе по-разному, например как коллекцию, состоящую 41 колес, дверей, сидений, окон использовать защищенные элементы класса. Производный класс может обращаться к защищенным элементам базового
остатка: 7%2 равно 1. При присваивании и арифметических операциях C++ выполняет все осмысленные и т.д. Или же, думая об автомобиле, можно представить себе его способность двигаться, увеличивать скорость, класса напрямую, используя оператор точку. Однако оставшаяся часть вашей программы может обращаться к
преобразования между основными типами, чтобы их можно было сочетать без ограничений: тормозить, останавливаться, парковаться и т.д. Класс позволяет инкапсулировать различные запчасти автомобиля и его защищенным элементам только с помощью интерфейсных функций этого класса. Таким образом, защищенные элементы
double d = 1; разнообразные функции в одну коллекцию, которая называется объектом. класса находятся между общими (доступными всей программе) и частными (доступными только самому классу)
int i = 1; Инкапсуляция всего, что мы знаем об автомобиле, в один класс имеет для программиста ряд преимуществ. Ведь все элементами.
d = d + i; сведения собраны вместе в одном объекте, на который легко ссылаться, копировать и манипулировать его данными. Чтобы обеспечить производным классам прямой доступ к определенным элементам базового класса, в то же время
i = d + i; Клиенты вашего класса, т.е. части программы, работающие с этим классом, могут использовать ваш объект, не защищая эти элементы от оставшейся части программы, C++ обеспечивает защищенные {protected) элементы класса.
Вот операции, создающие из основных типов новые типы: беспокоясь о том, что находится в нем или как именно он работает. Производный класс может обращаться к защищенным элементам базового класса, как будто они являются общими.
* указатель на Инкапсуляция – слияние данных и функции работающих с этими данными в одном объекте(класс) одновременно со Однако для оставшейся части программы защищенные элементы эквивалентны частным.
*const константный указатель на скрытием ненужной информации для использования объектом. Если в производном и базовом классе есть элементы с одинаковым именем, то внутри функций производного класса
& ссылка на Класс может состоять из любой комбинации типов переменных, а также типов других классов. Переменные в классе C++ будет использовать элементы производного класса. Если функциям производного класса необходимо обратиться к
[] вектор* называют переменными-членами или данными-членами. Класс Саt может иметь переменные-члены, представляющие элементу базового класса, вы должны использовать оператор глобального разрешения, например base class:: member.
() функция, возвращающая
Например:
сидения, радиоприемник, шины т.д.
Переменные-члены, известные также как данные-члены, принадлежат только своему классу. Переменные-члены — это • Также существуют дружественные функции, которые позволяют доступ к данным других классов.
char* p // указатель на символ такие же составные части класса, как колеса и мотор — составные части автомобиля. Используя ключевое слово friend, класс может сообщить C++, кто является его другом, т. е. другими словами,
char *const q // константный указатель на символ Функции в классе обычно выполняют действия над переменными-членами. Они называются функциями-членами или что другие классы могут обращаться напрямую к его частным элементам.
char v[10] // вектор из 10 символов методами класса. В число методов класса Саt могут входить Start() и Вreak(). Класс Саt может иметь такие данные-
Все вектора в качестве нижней границы индекса имеют ноль, поэтому в члены, которые представляют возраст и вес животного, а функциональная часть этого класса может быть представлена • Частные элементы класса защищают данные класса, следовательно, вы должны ограничить круг классов-
v десять элементов:v[0] ... v[9]. методами S1еер(), Meow() и ChaseMise(). друзей только теми классами, которым действительно необходим прямой доступ к частным элементам
Переменная указатель может содержать адрес объекта Функции-члены принадлежат своему классу, как и переменные-члены. Они оперируют переменными-членами и искомого класса.
соответствующего типа: определяют функциональные возможности класса.
char c;
// ...
Для объявления класса используйте ключевое слово class, за которым следует открывающая фигурная скобка, а за ней —
список данных-членов и методов класса. Объявление завершается закрывающей фигурной скобкой и точкой с запятой.
• C++ позволяет ограничить дружественный доступ определенным набором функций.
p = &c; // p указывает на c class abbott
Вот, например, как выглядит объявление класса Cat:
Унарное & является операцией взятия адреса. class Cat {
public:
Тип объекта может снабжаться квалификатором. Объявление объекта с квалификатором const указывает на то, что его {
unsigned int itsAge; friend costello;
значение далее не будет изменяться; объявляя объект как volatile (изменчивый, непостоянный (англ.)), мы указываем на // Общие элементы
его особые свойства для выполняемой компилятором оптимизации. Ни один из квалификаторов на диапазоны значений unsigned int itsWeight;
void Meow(); private:
и арифметические свойства объектов не влияет // Частные элементы
}
Помимо базовых типов существует практически бесконечный класс производных типов, которые формируются из уже При объявлении класса Cat память не резервируется. Это объявление просто сообщает компилятору о существовании };
существующих и описывают следующие конструкции: Классы-друзья C++ обычно не связаны между собой узами наследования. Единственный способ для таких не связанных
класса Cat, о том, какие данные он содержит (itsAge и itsWeight), а также о том, что он умеет делать (метод Meow()).
массивы объектов заданного типа; Кроме того, данное объявление сообщает компилятору о размере класса Cat, т.е. сколько места должен зарезер- между собой классов получить доступ к частным элементам другого класса состоит в том, чтобы этот другой класс
функции, возвращающие объекты заданного типа; информировал компилятор, что данный класс является другом.
вировать компилятор для каждого объекта класса Cat. Поскольку в данном примере для целого значения требуется
указатели на объекты заданного типа; четыре байта, то размер объекта Cat составит восемь байтов (четыре байта для переменной itsAge и четыре — для
структуры, содержащие последовательность объектов, возможно, различных заданных типов; itsWeight). Метод MeowO не требует выделения памяти в объекте.
объединения, каждое из которых может содержать любой из нескольких объектов различных заданных типов.
В общем случае приведенные методы конструирования объектов могут применяться рекурсивно.
Объект
Допускается выделять память и «использовать переменные», у которых нет имен,а также присваивать значения странно
выглядящим выражениям(*p[a+10]=10) Соответственно возникает потребность в названии для «чего-то в памяти».Это и
есть самое простое и фундаментальное понятие объекта – это есть непрерывная область памяти. Слово "lvalue"
первоначально было придумано для значения "нечто, что может стоять в левой части присваивания". Однако не всякий
адрес можно использовать в левой части присваивания; бывают адреса, ссылающиеся на константу.Lvalue- это
выражение ссылающееся на объект. Очевидным примером lvalue является идентификатор с соответствующим типом и
классом памяти. Существуют операции, порождающие lvalue. Например, если E - выражение типа указатель, то *E есть
выражение для lvalue, обозначающего объект, на который указывает E. Термин "lvalue" произошел от записи
присваивания E1 = E2, в которой левый (left - левый (англ.), отсюда буква l, value - значение) операнд E1 должен быть
выражением lvalue. Описывая каждый оператор, мы сообщаем, ожидает ли он lvalue в качестве операндов и выдает ли
lvalue в качестве результата.
Существуют два класса памяти: автоматический и статический. Несколько ключевых слов в совокупности с контекстом
объявлений объектов специфицируют класс памяти для этих объектов.
Автоматические объекты локальны в блоке, при выходе из него они "исчезают". Объявление, заданное внутри блока,
если в нем отсутствует спецификация класса памяти или указан спецификатор auto, создаст автоматический объект.
Объект, помеченный в объявлении словом register, является автоматическим и размещается по возможности в регистре
машины.
символом тильды (~), например ~employee. В своей программе вы определяете деструктор точно так же, как и любой
другой метод класса.

3 Конструкторы и деструкторы.
При создании объектов одной из наиболее широко используемых операций является инициализация элементов данных
объекта. единственным способом, с помощью которого можное обратиться к частным элементам данных, является 5 Абстрактные классы.
использование функций класса. Чтобы упростить процесс инициализации элементов данных класса, C++ использует 4 Производные классы, наследование.
Наследование - один из основополагающих принципов объектно-ориентированного программирования. Под Абстрактный механизм класса поддерживает понятие общей концепции, типа формы, из которой только могут
специальную функцию, называемую конструктором, которая запускается для каждого создаваемого вами объекта. фактически использоваться более конкретные варианты, типа круга и квадрата. Абстрактный класс может также
Подобным образом C++ обеспечивает функцию, называемую деструктором, которая запускается при уничтожении наследованием понимают возможность объявления производных типов на основе ранее объявленных типов. Как
известно, в C++ существует фиксированное множество элементарных типов. Это абсолютно независимые типы и использоваться, чтобы определить интерфейс, для которого производные классы обеспечивают разнообразие
объекта. Следующие основные концепции: выполнения.
объявление одного элементарного типа на основе другого в принципе невозможно. В случае наследования новый класс в
• Конструктор представляет собой метод класса, который облегчает вашим программам инициализацию буквальном смысле создаётся на основе ранее объявленного класса, НАСЛЕДУЕТ, а возможно и модифицирует его
данные и функции. Объявленный класс может служить основой (базовым классом) для новых производных классов.
Абстрактный класс - класс, который может использоваться только как базовый класс некоторого другого класса; никакие
объекты абстрактного класса не могут быть созданы кроме как подобъекты класса, производного от него. Класс
элементов данных класса. Производный класс наследуют данные и функции своих базовых классов и добавляют собственные компоненты. абстрактен, если он имеет по крайней мере одну чисто виртуальную функцию. Виртуальная функция определена как и
то виртуальная при использовании чистого спецификатора, pure-specifier, в объявлении функции в объявлении класса.
• Конструктор имеет такое же имя, как и класс.
Каждый производный класс при объявлении наследует свойства, данные методы лишь одного базового класса и имеют
свои собственные. В качестве базового класса можно использовать лишь полностью объявленные классы. Неполного Чисто виртуальная функция должна быть определенной, только если она явно вызывалась с синтаксисом qualified-id .
[Пример:

предварительного объявления здесь недостаточно. Для наглядного представления структуры производных классов
Конструктор не имеет возвращаемого значения. используются так называемые направленные ациклические графы. Узлы этого графа представляют классы, дуги - class point { /* ... */ };
отношение наследования. class shape { // абстрактный класс
• Каждый раз, когда ваша программа создает переменную класса, C++ вызывает конструктор класса, если Синтаксис наследования задаётся необязательным элементом заголовка класса, который называется спецификацией базы
и описывается следующим множеством форм Бэкуса-Наура:
point center;
// ...
конструктор существует. public:
СпецификацияБазы ::= : СписокБаз
• Многие объекты могут распределять память для хранения информации; когда вы уничтожаете такой объект, C++ СписокБаз ::= [СписокБаз,] ОписательБазы
ОписательБазы ::= ПолноеИмяКласса
point where() { return center; }
void move(point p) { center=p; draw(); }
будет вызывать специальный деструктор, который может освобождать эту память, очищая ее после объекта. ::= [virtual] [СпецификаторДоступа] ПолноеИмяКласса virtual void rotate(int) = 0; // чисто виртуальная
virtual void draw() = 0; // чисто виртуальная
• Деструктор имеет такое же имя, как и класс, за исключением того, что вы должны предварять его имя символом
::= [СпецификаторДоступа] [virtual] ПолноеИмяКласса
Список базовых классов может быть задан в определении класса с использованием нотации: // ...
тильды (~). основной пункт(base-clause): };]
[Замечание: объявление функции не может обеспечить и pure-specifier, и определение]

основной список спецификатора(base-specifier-list):
Деструктор не имеет возвращаемого значения. основной список спецификатора(base-specifier-list): [Пример:
основной спецификатор(base-specifier) struct C {
основной список спецификатора (base-specifier-list) , основной спецификатор (base-specifier) virtual void f() = 0 { }; // плохо сформировано
основной спецификатор (base-specifier): };]
Если ваша программа определяет конструктор, C++ будет автоматически вызывать его каждый раз, когда вы создаете ::opt nested-name-specifieropt class-name Абстрактный класс не должен использоваться как тип параметра, как тип возвращения функции, или как тип явного
объект. Программа также определяет конструктор с именем employee который присваивает начальные значения virtual access-specifieropt ::opt nested-name-specifieropt class-name преобразования. Указатели и ссылки на абстрактный класс могут быть объявлены.
объекту. Однако конструктор не возвращает никакого значения, несмотря на то, что он не объявляется как void. access-specifier virtualopt ::opt nested-name-specifieropt class-name [Пример:
Вместо этого вы просто не указываете тип возвращаемого значения: спецификатор доступа (access-specifier): shape x; // ошибка: объект абстрактного класса
class employee private - частный shape* p; // правильно
{ protected - защищенный shape f(); // ошибка
public: public - открытый void g(shape); // ошибка
employee(char *, long, float); //Конструктор Название класса в base-specifier не должно быть не полностью определенным классом; этот класс называют прямым shape& h(shape&); // правильно]
void show_employee(void); базовым классом для объявляемого класса. Во время поиска для имени базового класса, имена с неопределенным типом Класс абстрактен, если он содержит или наследует по крайней мере одну чисто виртуальную функцию, для которой
int change_salary(float); игнорируются. Если найденное имя не является названием класса, - программа плохо сформирована. Класс B - базовый заключительный overrider является чисто виртуальной функцией.
long get_id(void); класс класса D, если это - прямой базовый класс класса D или прямой базовый класс одного из базовых классов D. [Пример:
private: Говорят, что класс, (прямо или косвенно) получен из его (прямого или косвенного) базового класса. Члены базового class ab_circle : public shape {
char name [64]; класса, являются членами производного класса, если они не переопределены в производном классе. Члены базового int radius;
long employee_id; класса, как говорят, унаследованы производным классом. Унаследованные члены могут быть упомянуты в выражениях в public:
float salary; той же самой манере как другие члены производного класса, если их названия не скрыты или не неоднозначны может void rotate(int) {}
}; использоваться, чтобы обратиться к члену прямого или косвенного базового класса явно. Это позволяет обратиться к // ab_circle::draw() чисто виртуальная
Конструктор представляет собой специальную функцию, которую C++ автоматически вызывает каждый раз при названию базового класса, которое было переопределено в производном классе. Производный класс сам может };
создании объекта. Обычное назначение конструктора заключается в инициализации элементов данных объекта. использоваться для доступа к объектам базового класса. Указатель на производный класс может быть неявно Так как shape::draw() - чисто виртуальная функция, то ab_circle::draw() - чисто виртуальная по умолчанию.
Конструктор имеет такое же имя, как и класс. Например, класс с именем file использует конструктор с именем преобразован к указателю на доступный однозначный базовый класс. Левая часть выражения (lvalue) с типом Альтернативное объявление,
file. Вы определяете конструктор внутри своей программы так же, как и любой метод класса. Единственное производного класса может быть приведена(be bound) к ссылке на доступный однозначный базовый класс . class circle : public shape {
различие заключается в том, что конструктор не имеет возвращаемого значения. Когда вы позже объявляете #Include<> int radius;
объект, вы можете передавать параметры конструктору, как показано ниже: Class A{ class B public:
class_name object(valuel, value2, value3) Public; {public; void rotate(int) {}
Альтенативу использованию нескольких функций (перегруженных) составлет описание конструктора, который Void func() Void func() void draw(); // требуется определение
по заданному double создает complex. Например: {cout<<”A_func”<<endl}}; {cout<<”B_func”<<}}; };
class complex { Void main() сделало бы класс circle неабстрактным, и понадобилось бы определение circle::draw().]
// ... {B my_class: [Замечание: абстрактный класс может быть получен из класса, который не абстрактен, и чисто виртуальная функция
complex(double r) { re=r; im=0; } My_class func();} может перекрыть виртуальную функцию, которая не является чисто виртуальной.]
}; My_class A::func() Члены функции, могут быть вызваны из конструктора (или деструктора) абстрактного класса; эффект выполнения
Конструктор, требующий только один параметр, необязательно вызывать явно: прямого или косвенного виртуального обращения таким конструктором (или деструктором) к чисто виртуальной
complex z1 = complex(23); функции для создаваемого (или уничтожаемого) объекта не определен.
complex z2 = 23; 6 Инициализация объектов.
И z1, и z2 будут инициализированы вызовом complex(23). С помощью иниц-объявителя можно указать начальное значение объявляемого объекта. Инициализатору,
Конструктор - это предписание, как создавать значение данного типа. Когда требуется значение типа, и когда такое представляющему собой выражение или список инициализаторов, заключенный в фигурные скобки, предшествует знак
значение может быть создано конструктором, тогда, если такое значение дается для присваивания, вызывается =. Этот список может завершаться запятой; ее назначение сделать форматирование более четким.
конструктор. Например, класс complex можно инициализатор:
было бы описать так: выражение-присваивания
class complex { { список-инициализаторов }
double re, im; { список-инициализаторов, }
public:
complex(double r, double i = 0) { re=r; im=i; } список-инициализаторов:
friend complex operator+(complex, complex); инициализатор
friend complex operator*(complex, complex); список-инициализаторов , инициализатор
}; В инициализаторе статического объекта или массива все выражения должны быть константными. Если инициализатор
и действия, в которые будут входить переменные complex и целые константы, стали бы допустимы. Целая auto- и register-объекта или массива находится в списке, заключенном в фигурные скобки, то входящие в него выражения
константа будет интерпретироваться как complex с нулевой мнимой частью. Например, также должны быть константными. Однако в случае автоматического объекта с одним выражением инициализатор не
a=b*2 означает: обязан быть константным выражением, он просто должки иметь соответствующий объекту тип.
a=operator*( b, complex( double(2), double(0) ) ) В первой редакции не разрешалась инициализация автоматических структур, объединений и массивов. ANSI-стандарт
Определенное пользователем преобразование типа применяется неявно только тогда, когда оно является едиственным. позволяет это; однако, если инициализатор не может быть представлен одним простым выражением, инициализация
Объект, сконструированный с помощью явного или неявного вызова может быть выполнена только с помощью константных конструкций.
конструктора, является автоматическим и будет уничтожен при первой возможности, обычно сразу же после Статический объект, инициализация которого явно не указана, инициализируется так, как если бы ему (или его
оператора, в котором он был создан. элементам) присваивалась константа 0. Начальное значение автоматического объекта, явным образом не
Конструктор можно рассматривать двояко — как функцию, инициализирующую объект, или, с инициализированного, не определено.
позиций математики, как отображение аргументов конструктора на домен класса. Инициализатор указателя или объекта арифметического типа - это одно выражение (возможно, заключенное в фигурные
Деструктор представляет собой функцию, которую C++ автоматически запускает, когда он или ваша программа скобки), которое присваивается объекту.
уничтожает объект. Деструктор имеет такое же имя, как и класс объекта; однако вы предваряете имя деструктора
int ival = (int) 3.14159;
extern char *rewrite_str( char* );
char *pc2 = rewrite_str( (char*) pc );
int addr_va1ue = int( &iva1 );
9 Динамическое определение типов
8 Преобразование типов RTTI позволяет программам, которые манипулируют объектами через указатели или ссылки на базовые классы,
Неявное преобразование типов получить истинный производный тип адресуемого объекта. Для поддержки RTTI в языке C++ есть два оператора:


арифметическое выражение с операндами разных типов: все операнды приводятся к наибольшему типу из
7 Множественное наследование. встретившихся. Это называется арифметическим преобразованием. Например: оператор dynamic_cast поддерживает преобразования типов во время выполнения, обеспечивая безопасную
В C++ производный класс может быть порождён из любого числа непосредственных базовых классов. Наличие у int ival = 3; навигацию по иерархии классов. Он позволяет трансформировать указатель на базовый класс в указатель на
производного класса более чем одного непосредственного базового класса называется множественным наследием. В double dva1 = 3.14159; производный от него, а также преобразовать l-значение, ссылающееся на базовый класс, в ссылку на
этом случае производный класс наследует методы и данные обоих классов.Синтаксически множественное наследование // ival преобразуется в double: 3.0 производный, но только в том случае, если это завершится успешно;
отличается от единичного наследования списком баз, состоящим более чем из одного элемента. ival + dva1;
class A { }; class B { }; class C : public A, public B { }; присваиваем значения выражения одного типа объекту другого типа. В этом случае результирующим является тип
объекта, которому значение присваивается. Так, в первом примере литерал 0 типа int присваивается указателю типа int*,
• оператор typeid позволяет получить фактический производный тип объекта, адресованного
При создании объектов-представителей производного класса, порядок расположения непосредственных базовых классов
указателем или ссылкой.
в списке баз определяет очерёдность вызова конструкторов умолчания. Этот порядок влияет и на очерёдность вызова значение которого будет 0. Во втором примере double преобразуется в int.
void company::payroll( employee *pe )
деструкторов при уничтожении этих объектов. Но эти проблемы, также как и алгоритмы выделения памяти для базовых // 0 преоразуется в нулевой указатель типа int*
{
объектов, скорее всего, относятся к вопросам реализации. Более существенным является ограничение, согласно int *pi = 0;
if ( programmer *pm = dynamic_cast< programmer* >( pe ) ) {
которому одно и то же имя класса не может входить более одного раза в список баз при объявлении производного // dva1 преобразуется в int: 3
}
класса. Это означает, что в наборе непосредственных базовых классов, которые участвуют в формировании ivat = dva1;
else {…
производного класса не должно встречаться повторяющихся элементов. перегрузка функции аргумента, тип которого отличается от типа соответствующего формального параметра. Тип
}
Вместе с тем, один и тот же класс может участвовать в формировании нескольких (а может быть и всех) фактического аргумента приводится к типу параметра:
}
непосредственных базовых классов данного производного класса. Так что для непрямых базовых классов, участвующих extern double sqrt( double );
В предыдущем примере операция dynamic_cast преобразует указатель на базовый класс в указатель на производный. Ее
в формировании производного класса не существует никаких ограничений на количество вхождений в объявление // 2 преоразуется в double: 2.0
также можно применять для трансформации l-значения типа базового класса в ссылку на тип производного.
производного класса: cout << "Квадратный корень из 2: " << sqrt( 2 ) << endt;
if ( programmer *pm = dynamic_cast< programmer* >( pe ) )
class A { public: int x0, xA; }; class B : public A { public: int xB; }; class C : public A { public: int x0, xC; }; class D : public B, возвращает из функции значения, тип которого не совпадает с типом возвращаемого результата, заданным в объявлении
Для извещения об ошибке в случае приведения к ссылочному типу оператор dynamic_cast возбуждает исключение.
public C { public: int x0, xD; }; функции. Тип фактически возвращаемого значения приводится к объявленному. Например:
Следовательно, предыдущий пример можно записать так:
В этом примере класс A дважды используется при объявлении класса D в качестве непрямого базового класса. Знак :: double difference( int ivati, int iva12 )
#include < typeinfo>
означает расширение области видимости. это необходимо чтобы получить методы базового класса. { // результат преобразуется в double
void company::payroll( employee &re )
Статусы множественного наследования: return ivati - iva12;
{
Класс может быть объявлен с помощью служебных классов Class или Struct. Когда объявляется Struct – то класс }
try {
открытый,а когда Class – закрытый. На доступ к классу влияет не только явное использование спецификатора , но и Арифметические преобразования типов
programmer &rm = dynamic_cast< programmer & >( re );
выбор ключевого слова(Class/ Struct) Арифметические преобразования приводят оба операнда бинарного арифметического выражения к одному типу,
// eniieuciaaou rm aey auciaa programmer::bonus()
При множественном наследовании актуальной становится проблема неоднозначности, связанная с доступом к членам который и будет типом результата выражения. Два общих правила таковы:
}
базовых классов. Доступ к члену базового класса является неоднозначным, если выражение доступа именует более • типы всегда приводятся к тому из типов, который способен обеспечить наибольший диапазон значений при
catch ( std::bad_cast ) {
одной функции, объекта (данные-члены класса также являются объектами), типа (об этом позже!) или перечислителя. наибольшей точности. Это помогает уменьшить потери точности при преобразовании; // eniieuciaaou ooieoee-?eaiu eeanna employee
Например, неоднозначность содержится в следующем операторе: • любое арифметическое выражение, включающее в себя целые операнды типов, меньших чем int, приводится к }
MyD.xA = 100; int }
здесь предпринимается неудачная попытка изменения значения данного члена базового фрагмента объекта MyD. рассмотрим иерархию правил преобразований, начиная с наибольшего типа long double.
Выражение доступа MyD.xA именует сразу две переменных xA. Разрешение неоднозначности сводится к построению один из операндов имеет тип long double, второй приводится к этому же типу в любом случае. Например, в следующем
такого выражения доступа, которое однозначно указывало бы функцию, объект, тип (об этом позже!) или перечислитель. выражении символьная константа 'a' трансформируется в long double (значение 97 представления ASCII) и затем
Наша очередная задача сводится к описанию однозначных способов доступа к данным-членам класса, расположенным в прибавляется к литералу того же типа: 3.14159L + 'a'. 11 Обработка ошибок. Обработка исключительных ситуаций.
разных базовых фрагментах объекта. И здесь мы впервые сталкиваемся с ограниченными возможностями операции Если в выражении нет операндов long double, но есть double, все преобразуются к этому типу. Например: Обработка исключений – это механизм, позволяющий двум независимо разработанным программным компонентам
доступа. int iva1; взаимодействовать в аномальной ситуации, называемой исключением.
MyD.B::x0 = 100; float fval; Исключение – это аномальное поведение во время выполнения, которое программа может обнаружить, например:
Этот оператор обеспечивает изменение значения данного-члена базового фрагмента - представителя класса B. Здесь нет double dval; деление на 0, выход за границы массива или истощение свободной памяти. Такие исключения нарушают нормальный
никаких проблем, поскольку непосредственный базовый класс B наследует данные-члены базового класса A. Поскольку // fva1 и iva1 преобразуются к double перед сложением ход работы программы, и на них нужно немедленно отреагировать.
в классе B отсутствуют данные-члены с именем x0, транслятор однозначно определяет принадлежность этого элемента. dval + fva1 + ival; Инструкции, которые могут возбуждать исключения, должны быть заключены в try-блок. Такой блок начинается с
Итак, доступ к данному-члену базового класса A "со стороны" непосредственного базового класса B не представляет В том случае, если нет операндов типа double и long double, но есть float, тип операндов меняется на float: ключевого слова try, за которым идет последовательность инструкций, заключенная в фигурные скобки, а после этого –
особых проблем. Явное преобразование типов список обработчиков, называемых catch-предложениями. Try-блок группирует инструкции программы и ассоциирует с
class computer : public computer_screen, public mother_board Производится при помощи следующих операторов: static_cast, dynamic_cast, const_cast и reinterpret_cast. Преобразование ними обработчики исключений. Куда нужно поместить try-блоки в функции main(), чтобы были обработаны исключения
{ с помощью указателей в тип void* и обратно popOnEmpty и pushOnFull?
public: Void указывает на объект любого неконстантного типа может быть присвоен указателю типа void*, который for ( int ix = 1; ix < 51; ++ix ) {
computer(char *, int, float, char *, long, int, int, int, int, int); используется в тех случаях, когда действительный тип объекта либо неизвестен, либо может меняться в ходе try { // try-блок для исключений pushOnFull
void show_computer(void); выполнения программы. Поэтому указатель void* и называют универсальным указателем. Например: if ( ix % 3 == 0 )
private: int iva1; stack.push( ix );
char name[64]; int *pi = 0; }
int hard_disk; char *pc = 0; catch ( pusOnFull ) { ... }
float floppy; void *pv; if ( ix % 4 == 0 )
} pv = pi; // правильно: неявное преобразование stack.display();
pv = pc; // правильно: неявное преобразование try { // try-блок для исключений popOnEmpty
const int *pci = &iva1; if ( ix % 10 == 0 ) {
pv = pci; // ошибка: pv имеет тип, отличный от const void*; int dummy;
const void *pcv = pci; // правильно указатель void* не может быть разыменован непосредственно. stack.pop( dummy );
Синтаксис операции явного преобразования типов таков: stack.display();
cast-name< type >( expression ); }
Здесь cast-name – одно из ключевых слов static_cast, const_cast, dynamic_cast или reinterpret_cast, а type – тип, к которому }
приводится выражение expression. catch ( popOnEmpty ) { ... }
const_cast служит для трансформации константного типа в неконстантный и подвижного (volatile) – в неподвижный. }
С применением static_cast осуществляются те преобразования, которые могут быть сделаны неявно, на основе правил по Catch-обработчик состоит из трех частей: ключевого слова catch, объявления одного типа или одного объекта,
умолчанию: заключенного в круглые скобки (оно называется объявлением исключения), и составной инструкции. Если для обработки
double d = 97.0; исключения выбрано некоторое catch-предложение, то выполняется эта составная инструкция.
char ch = static_cast< char >( d ); Может оказаться так, что в одном предложении catch не удалось полностью обработать исключение. Выполнив
Кроме того, с помощью static_cast указатель void* может преобразовать в указатель определенного типа, некоторые корректирующие действия, catch-обработчик может решить, что дальнейшую обработку следует поручить
арифметическое значение – в значение перечисления (enum). функции, расположенной "выше" в цепочке вызовов. Передать исключение другому catch-обработчику можно с
enum mumble { first = 1, second, third }; помощью повторного возбуждения исключения. Для этой цели в языке предусмотрена конструкция
extern int ival; throw;
mumble mums_the_word = static_cast< mumble >( ival ); которая вновь генерирует объект-исключение. Повторное возбуждение возможно только внутри составной инструкции,
reinterpret_cast работает с внутренними представлениями объектов. Правильность этой операции целиком зависит от являющейся частью catch-обработчика:
программиста. Например: catch ( exception eObj ) {
complex<double> *pcom; if ( canHandle( eObj ) )
char *pc = reinterpret_cast< char* >( pcom ); // обработать исключение
Программа не должен забыть или упустить из виду, какой объект реально адресуется указателем char* pc. return;
dynamic_cast применяется при идентификации типа во время выполнения (run-time type identification). else
Устаревшая форма явного преобразования // повторно возбудить исключение, чтобы его перехватил другой
Устаревшая форма явного преобразования имеет два вида: // catch-обработчик
// появившийся в C++ вид throw;
type (expr); }
// вид, существует в C
(type) expr;
и может использоваться вместо операторов static_cast, const_cast и reinterpret_cast.
const char *pc = (const char*) pcom;
10 Указатель на член класса 15 Многофайловые взаимодействие
Поскольку нестатические функции-члены формально, а нестатические данные-члены фактически не существуют без С помощью ключевого слова extern, аналогичного объявлению функции: оно указывает, что объект определен в другом
объекта-представителя класса, определение указателя на компонент класса (член класса или функцию-член) отличается месте – в этом же исходном файле или в другом. Например:
от определения указателя на объект или обычную функцию. extern int i;
Для объявления указателя на нестатическую функцию используется специальная синтаксическая конструкция, Эта инструкция “обещает”, что в программе имеется определение, подобное
состоящая из спецификатора объявления и заключённого в скобки квалифицированного имени указателя, состоящего из int i;
имени класса, операции доступа к члену класса ::, разделителя * , собственно имени указателя, закрывающей скобки и extern-объявление не выделяет места под объект. Оно может встретиться несколько раз в одном и том же исходном файле
списка параметров: или в разных файлах одной программы. Однако обычно находится в общедоступном заголовочном файле, который
Основные приёмы работы с указателями на функции-члены демонстрируются на следующих примерах: включается в те модули, где необходимо использовать глобальный объект:
class XXX // заголовочный файл
{ extern int obj1;
public: extern int obj2;
long x1; // исходный файл
int x2; int obj1 = 97;
/*Данные-члены класса.*/ int obj2;
long getVal1() {return x1;} Объявление глобального объекта с указанием ключевого слова extern и с явной инициализацией считается
long getVal2() {return x2*x1;} определением. Под этот объект выделяется память, и другие определения не допускаются:
/*Функции-члены класса без параметров.*/ extern const double pi = 3.1416; // определение
int getVal3(int param) {return x2*param;} const double pi; // ошибка: повторное определение pi
char* getVal4(char *str) {return str;} Ключевое слово extern может быть указано и при объявлении функции – для явного обозначения его подразумеваемого
/*Функции-члены класса с параметрами.*/ смысла: “определено в другом месте”. Например:
static int f1() {return 100;} extern void putValues( int*, int );
static int f2() {return 10;} Одна из проблем, вытекающих из возможности объявлять объект или функцию в разных файлах, – вероятность
static int f3(int param) {return param;} несоответствия объявлений или их расхождения в связи с модификацией программы. В С++ имеются средства,
/* Определение различных статических функций*/ помогающие обнаружить такие различия.
XXX(long val1, int val2){x1 = val1; x2 = val2;} Предположим, что в файле token.C функция addToken() определена как имеющая один параметр типа unsigned char. В
/*Конструктор.*/ файле lex.C, где эта функция вызывается, в ее определении указан параметр типа char.
}; // ---- в файле token.C ----
void main() int addToken( unsigned char tok ) { /* ... */ }
{ // ---- в файле lex.C ----
XXX q(1,2);/* Определение объекта.*/ extern int addToken( char );
XXX* pq = new (XXX); Вызов addToken() в файле lex.C вызывает ошибку во время связывания программы. Избежать подобных неточностей
pq->x1 = 100; поможет прежде всего правильное использование заголовочных файлов.
pq->x2 = 100; Заголовочный файл предоставляет место для всех extern-объявлений объектов, объявлений функций и определений
/*Определение и инициализация объекта по указателю.*/ встроенных функций. Это называется локализацией объявлений. Те исходные файлы, где объект или функция
long (XXX::*fp_0) (); определяется или используется, должны включать заголовочный файл.
/*Указатель на функцию-член класса.*/ Такие файлы позволяют добиться двух целей. Во-первых, гарантируется, что все исходные файлы содержат одно и то же
long (XXX::*fp_1) () = &XXX::getVal1; объявление для глобального объекта или функции. Во-вторых, при необходимости изменить объявление это изменение
/*Проинициализированный указатель на функцию-член класса. Его значение является относительной величиной и делается в одном месте, что исключает возможность забыть внести правку в какой-то из исходных файлов.
равняется значению смещения функции-члена относительно первого члена класса. */ Пример с addToken() имеет следующий заголовочный файл:
fp_0 = XXX::getVal1; // ----- token.h -----
/* Инициализация первого указателя. Один и тот же указатель можно настраивать на различные функции-члены класса. typedef unsigned char uchar;
Главное, чтобы у всех этих функций-членов совпадали списки параметров и возвращаемые значения функций. */ const uchar INLINE = 128;
long val_1 = (q.*fp1)(); const uchar IT = ...;
/*Вызов функции-члена класса по указателю из объекта.*/ const uchar GT = ...;
long val_2 = (pq->*fp0)(); extern uchar lastTok;
/*Вызов функции-члена класса по указателю с помощью указателя на объект.*/ extern int addToken( uchar );
int (XXX::*fp_3) (int) = &XXX::getVal3; inline bool is_relational( uchar tok )
/* Проинициализированный указатель на функцию-член класса. С параметрамиь типа int. */ { return (tok >= LT && tok <= GT); }
int val_3 = (q.*fp_3)(6); // ----- lex.C -----
/* Вызов функции-члена класса по указателю из объекта с передачей параметров.*/ #include "token.h"
char* (XXX::*fp_4) (char) = &XXX::getVal3; // ----- token.C -----
/*Проинициализированный указатель на функцию-член класса с параметрами типа int.*/ #include "token.h"
char val_4 = (pq->*fp4)("new string"); При проектировании заголовочных файлов нужно учитывать несколько моментов. Все объявления такого файла должны
/*Вызов функции-члена класса по указателю с помощью указателя на объект.*/ быть логически связанными.
int (*fp_5) () = &XXX::f1; При проектировании заголовочных файлов нужно учитывать несколько моментов. Все объявления такого файла должны
/*Указатель на статическую функцию объявляется без спецификации класса. Явная спецификация класса необходима быть логически связанными. Если он слишком велик или содержит слишком много не связанных друг с другом
лишь при инициализации указателя. */ элементов, программисты не станут включать его, экономя на времени компиляции. Для уменьшения временных затрат
int retval = (*fp_5)(); в некоторых реализациях С++ предусматривается использование предкомпилированных заголовочных файлов
/*Вызов статической функции по указателю.*/
fp_5 = XXX::f2;
/*Перенастройка статического указателя. Главное требование – совпадение списков параметров и типа возвращаемого
значения.*/
int (*fp_6) (int) = &XXX::f3;
/*Указатель на статическую функцию с параметрами.*/
int retval = (*fp_6)(255);
/*Вызов статической функции с параметрами по указателю.*/
long (XXX::*px1) = &XXX::x1;
/*Определили и проинициализировали указатель на член класса long*/
q.*px11 = 10;
/*Используя указатель на компоненту класса, изменили значение переменной x1 объекта q, представляющего класс
XXX. */
pq->*px11 = 10;
/*Используя указатель на компоненту класса, изменили значение переменной x1 объекта, представляющего класс XXX и
расположенного по адресу pq. */
}
Вызов статических функций-членов класса не требует никаких объектов и указателей на объекты. От обычных
функций их отличает лишь специфическая область видимости.
cerr <<"не могу открыть "copy.out" для записи\n";
exit( -1 );
}
Класс ofstream является производным от ostream. Все определенные в ostream операции применимы и к ofstream.
Объект класса fstream может также открывать файл одновременно для ввода и вывода. Например, приведенная
инструкция открывает файл word.out для ввода и дозаписи:
12 Потоки fstream io( "word.out", ios_base::in|ios_base::app );
Для использования библиотеки iostream в программе необходимо включить заголовочный файл Для задания нескольких режимов используется оператор побитового ИЛИ. Объект класса fstream можно
#include <iostream> позиционировать с помощью функций-членов seekg() или seekp(). Здесь буква g обозначает позиционирование для
Операции ввода/вывода выполняются с помощью классов istream (потоковый ввод) и ostream (потоковый вывод). Третий чтения (getting) символов (используется с объектом класса ofstream), а p – для записи (putting) символов (используется с
класс, iostream, является производным от них и поддерживает двунаправленный ввод/вывод. Для удобства в библиотеке объектом класса ifstream). Эти функции делают текущим тот байт в файле, который имеет указанное абсолютное или
определены три стандартных объекта-потока: относительное смещение. У них есть два варианта: 13 Шаблоны функций
• cin – объект класса istream, соответствующий стандартному вводу. В общем случае он позволяет читать
// установить абсолютное смещение в файле
seekg( pos_type current_position )
Шаблон дает алгоритм, используемый для автоматической генерации экземпляров функций с различными типами.
Программист параметризует все или только некоторые типы в интерфейсе функции (т.е. типы формальных параметров и
данные с терминала пользователя; // смещение от текущей позиции в том или ином направлении возвращаемого значения), оставляя ее тело неизменным. Функция хорошо подходит на роль шаблона, если ее
seekg( off_type offset_position, ios_base::seekdir dir );
• cout – объект класса ostream, соответствующий стандартному выводу. В общем случае он позволяет В первом варианте текущая позиция устанавливается в некоторое абсолютное значение, заданное аргументом
реализация остается инвариантной на некотором множестве экземпляров, различающихся типами данных
Шаблон функции начинается с ключевого слова template, за которым в угловых скобках следует список параметров.
выводить данные на терминал пользователя; current_position, причем значение 0 соответствует началу файла. Например, если файл содержит такую Затем следует объявление функции:
последовательность символов:

template< class T >
cerr – объект класса ostream, соответствующий стандартному выводу для ошибок. В этот поток мы abc def ghi jkl void sort( T array[], int size ); // шаблон sort объявлен, но не определён
то вызов
направляем сообщения об ошибках программы. io.seekg( 6 );
Вывод осуществляется, как правило, с помощью перегруженного оператора сдвига влево (<<), а ввод – с помощью template< class T >
позиционирует io на шестой символ, т.е. на f. Второй вариант устанавливает указатель рабочей позиции файла на void sort( T array[], int size ) // объявление и определение
оператора сдвига вправо (>>) заданное расстояние от текущей, от начала файла или от его конца в зависимости от аргумента dir, который может
Помимо чтения с терминала и записи на него, библиотека iostream поддерживает чтение и запись в файлы. Для этого {
принимать следующие значения: /* сортировка */
предназначены следующие классы:

• ifstream, производный от istream, связывает ввод программы с файлом;


• ios_base::beg – от начала файла;
}
template< int BufferSize > // целочисленный параметр


char* read()
• ofstream, производный от ostream, связывает вывод программы с файлом;
ios_base::cur – от текущей позиции; {
char *Buffer = new char[ BufferSize ];

• fstream, производный от iostream, связывает как ввод, так и вывод программы с файлом.
• ios_base::end – от конца файла. /* считывание данных */
return Buffer;
Строковые потоки
Чтобы использовать часть библиотеки iostream, связанную с файловым вводом/выводом, необходимо включить в }
Библиотека iostream поддерживает операции над строковыми объектами в памяти. Класс ostringstream вставляет
программу заголовочный файл Вообще говоря, для вызова шаблонной функции, необходимо указать значения для всех параметров шаблона. Для этого
символы в строку, istringstream читает символы из строкового объекта, а stringstream может использоваться как для
#include <fstream> после имени шаблона указывается список значений в угловых скобках:
чтения, так и для записи. Чтобы работать со строковым потоком, в программу необходимо включить заголовочный файл
Библиотека iostream поддерживает также ввод/вывод в область памяти, при этом поток связывается со строкой в памяти int i[5] = { 5, 4, 3, 2, 1 };
#include <sstream>
программы. С помощью потоковых операторов ввода/вывода мы можем записывать данные в эту строку и читать их sort< int >( i, 5 );
#include <iostream>
оттуда. Объект для строкового ввода/вывода определяется как экземпляр одного из следующих классов: #include <sstream>
char c[] = "бвгда";
• istringstream, производный от istream, читает из строки;
int main()
{
sort< char >( c, strlen( c ) );
sort< int >( c, 5 ); // ошибка: у sort< int > параметр int[] а не char[]
• ostringstream, производный от ostream, пишет в строку;
int ival = 1024; int *pival = &ival;
double dval = 3.14159; double *pdval = &dval;
char *ReadString = read< 20 >;
delete [] ReadString;
ostringstream format_message;
• stringstream, производный от iostream, выполняет как чтение, так и запись. // преобразование значений в строковое представление
ReadString = read< 30 >;
Для каждого набора параметров компилятор генерирует новый экземпляр функции. Процесс создания нового
Для использования любого из этих классов в программу нужно включить заголовочный файл format_message << "ival: " << ival
экземпляра называется инстанцированием шаблона. Экземляр функции называется специализацией.
#include <sstream> <<" адрес ival: " << pival << 'n'
Так выглядит шаблон функции минимума в C++:
Состояния Потока << "dval: " << dval
template< class T >
Каждый поток (istream или ostream) имеет ассоциированное с ним состояние, и обработка ошибок и нестандартных << " адрес dval: " << pdval << endl;
T min( T a, T b )
условий осуществляется с помощью соответствующей установки и проверки этого состояния. string msg = format_message.str();
{
Поток может находиться в одном из следующих состояний: cout << " размер строки сообщения: " << msg.size()
if( a < b ) return a;
enum stream_state { _good, _eof, _fail, _bad }; <<" сообщение:"<<msg <<endl;
else return b;
Если состояние _good или _eof, значит последняя операция ввода прошла успешно. Если состояние _good, то }
}
следующая операция ввода может пройти успешно, в противном случае она закончится неудачей. Другими словами, Поток istringstream читает из объекта класса string, с помощью которого был сконструирован. В частности, он
Для вызова этой функции можно просто использовать её имя:
применение операции ввода к потоку, который не находится в состоянии _good, является пустой операцией. Если применяется для преобразования строкового представления числа в его арифметическое значение:
min( 1, 2 );
делается попытка читать в переменную v, и операция оканчивается неудачей, значение v должно остаться #include <iostream>
min( 'a', 'b' );
неизменным (оно будет неизменным, если v имеет один из тех типов, которые обрабатываются функциями членами #include <sstream>
min( string( "abc" ), string( "cde" ) );
istream или ostream). Отличия между состояниями _fail и _bad очень незначительно и представляет интерес только для #include <string>
Шаблоны классов
разработчиков операций ввода. В состоянии _fail предполагается, что поток не испорчен и никакие символы не int main()
В классе, реализующем связанный список (англ. Linked list) целых чисел, алгоритмы добавления нового элемента
потеряны. В состоянии _bad может быть все что угодно. {
списка, поиска нужного элемента не зависят от того, что элементы списка — целые числа. Те же алгоритмы
Состояние потока можно проверять например так: int ival = 1024; int *pival = &ival;
применялись бы и для списка символов, строк, дат, классов игроков, и так далее.
switch (cin.rdstate()) { double dval = 3.14159; double *pdval = &dval;
template< class T >
case _good: // создает строку, в которой значения разделены пробелами
class List
// последняя операция над cin прошла успешно ostringstream format_string;
{
break; format_string << ival << " " << pival << " "
/* ... */
case _eof: << dval <<" " << pdval << endl;
public:
// конец файла // извлекает сохраненные значения в коде ASCII
void Add( const T& Element );
break; // и помещает их в четыре разных объекта
bool Find( const T& Element );
case _fail: istringstream input_istring( format_string.str() );
/* ... */
// некоего рода ошибка форматирования input_istring >> ival >>pival
};
// возможно, не слишком плохая >> dval >> pdval;
Для использования шаблона класса, необходимо указать его параметры:
break; }
List<int> li;
case _bad: List<string> ls;
// возможно, символы cin потеряны li.Add( 17 );
break; ls.Add( "Hello!" );
} Параметры шаблонов
Файловый ввод/вывод Параметрами шаблонов могут быть: параметры-типы, параметры обычных типов, параметры-шаблоны.
Если программе необходимо работать с файлом, то следует включить в нее заголовочный файл fstream (который в свою Для параметров любого типа можно указывать значения по умолчанию.
очередь включает iostream): template< class T1, // параметр-тип
#include <fstream> typename T2, // параметр-тип
Если файл будет использоваться только для вывода, мы определяем объект класса ofstream. Например: int I, // параметр обычного типа
ofstream outfile( "copy.out", ios::base::out ); T1 DefaultValue, // параметр обычного типа
Передаваемые конструктору аргументы задают имя открываемого файла и режим открытия. Файл типа ofstream может template< class > class T3, // параметр-шаблон
быть открыт либо – по умолчанию – в режиме вывода (ios_base::out), либо в режиме дозаписи (ios_base::app). Такое class Character = char // параметр по умолчанию
определение файла outfile2 эквивалентно приведенному выше: >
// по умолчанию открывается в режиме вывода
ofstream outfile2( "copy.out" );
Если в режиме вывода открывается существующий файл, то все хранившиеся в нем данные пропадают. Если же мы
хотим не заменить, а добавить данные, то следует открывать файл в режиме дозаписи: тогда новые данные помещаются
в конец. Если указанный файл не существует, то он создается в любом режиме.
Прежде чем пытаться прочитать из файла или записать в него, нужно проверить, что файл был успешно открыт:
if ( ! outfile ) { // открыть файл не удалось