
- •Глава 6 посвящена понятию производных классов, которое позволяет строить
- •Раздел 3.4 главы 2. Для обозначения справочного руководства применяется
- •1991 Г.Г. (такие как множественное наследование, статические функции-члены
- •1.1 Введение
- •1.2 Парадигмы программирования
- •1.2.1 Процедурное программирование
- •1.2.5 Объектно-ориентированное программирование
- •1.5 Поддержка объектно-ориентированного программирования
- •1.5.1 Механизм вызова
- •1.5.2 Проверка типа
- •1.5.3 Множественное наследование
- •1.6 Пределы совершенства
- •2.2 Имена
- •2.3.2 Неявное преобразование типа
- •2.4 Литералы
- •2.4.4 Строки
- •2.6. Экономия памяти
- •2.6.1 Поля
- •3.1.1 Анализатор
- •3.1.2 Функция ввода
- •3.2 Сводка операций
- •3.2.3 Инкремент и декремент
- •3.2.5 Преобразование типа
- •3.2.6 Свободная память
- •3.3.2 Оператор goto
- •4.1 Введение
- •4.3.1 Единственный заголовочный файл
- •4.3.2 Множественные заголовочные файлы
- •4.4 Связывание с программами на других языках
- •4.6.3 Передача параметров
- •5.1 Введение и краткий обзор
- •5.3.1 Альтернативные реализации
- •5.3.2 Законченный пример класса
- •Vector и matrix, мы могли бы обойтись без контроля индекса при
- •5.4.5 Указатели на члены
- •5.4.6 Структуры и объединения
- •5.5.3 Свободная память
- •5.5.5 Массивы объектов класса
- •6.1 Введение и краткий обзор
- •6.2.3 Иерархия классов
- •6.2.4 Поля типа
- •6.4.1 Монитор экрана
- •6.5 Множественное наследование
- •7.1 Введение
- •7.3 Пользовательские операции преобразования типа
- •7.3.2 Операции преобразования
- •7.3.3 Неоднозначности
- •7.5 Большие объекты
- •Void f2(t a) // вариант с контролем
- •Void f3(t a) // вариант с контролем
- •Inv() обращает саму матрицу m, а не возвращает новую, обратную m,
- •7.13 Предостережения
- •8.1 Введение
- •8.4.4 Неявная передача операций
- •8.4.5 Введение операций с помощью параметров шаблонного класса
- •8.7.1 Задание реализации с помощью параметров шаблона
- •9.1 Обработка ошибок
- •9.1.2 Другие точки зрения на особые ситуации
- •9.3.2 Производные особые ситуации
- •9.4.2 Предостережения
- •9.4.3 Исчерпание ресурса
- •9.4.4 Особые ситуации и конструкторы
- •9.5 Особые ситуации могут не быть ошибками
- •10.1 Введение
- •10.2 Вывод
- •10.2.1 Вывод встроенных типов
- •10.4.1.2 Поля вывода
- •10.4.1.4 Вывод целых
- •Istream - шаблон типа smanip, а smanip - двойник для ioss.
- •10.5.1 Закрытие потоков
- •10.5.2 Строковые потоки
- •X Целый параметр выдается в шестнадцатеричной записи;
- •11.1 Введение
- •11.2 Цели и средства
- •11.3 Процесс развития
- •11.3.1 Цикл развития
- •11.3.2 Цели проектирования
- •11.3.3 Шаги проектирования
- •11.3.3.1 Шаг 1: определение классов
- •11.3.3.2 Шаг 2: определение набора операций
- •11.3.3.3 Шаг 3: указание зависимостей
- •11.3.3.4 Шаг 4: определение интерфейсов
- •11.3.3.5 Перестройка иерархии классов
- •11.3.3.6 Использование моделей
- •11.3.4 Эксперимент и анализ
- •11.3.5 Тестирование
- •11.3.6 Сопровождение
- •11.3.7 Эффективность
- •11.4 Управление проектом
- •11.4.1 Повторное использование
- •11.4.2 Размер
- •11.4.3 Человеческий фактор
- •11.5 Свод правил
- •11.6 Список литературы с комментариями
- •12.1 Проектирование и язык программирования.
- •12.1.1 Игнорирование классов
- •12.1.2 Игнорирование наследования
- •12.1.3 Игнорирование статического контроля типов
- •12.1.4 Гибридный проект
- •12.2 Классы
- •12.2.1 Что представляют классы?
- •12.2.2 Иерархии классов
- •12.2.3 Зависимости в рамках иерархии классов.
- •Vertical_scrollbar или с помощью одного типа scrollbar, который
- •12.2.6 Отношения использования
- •12.2.7 Отношения внутри класса
- •12.3 Компоненты
- •12.4 Интерфейсы и реализации
- •12.5 Свод правил
- •13.1 Введение
- •13.2 Конкретные типы
- •13.4 Узловые классы
- •1, 2, 6 И 7. Класс, который не удовлетворяет условию 6, походит
- •13.5.1 Информация о типе
- •13.6 Обширный интерфейс
- •13.7 Каркас области приложения
- •13.8 Интерфейсные классы
- •13.10 Управление памятью
3.2.6 Свободная память
Именованный объект является либо статическим, либо автоматическим
(см.$$2.1.3). Статический объект размещается в памяти в момент запуска
программы и существует там до ее завершения. Автоматический объект
размещается в памяти всякий раз, когда управление попадает в блок,
содержащий определение объекта, и существует только до тех пор, пока
управление остается в этом блоке. Тем не менее, часто бывает удобно
создать новый объект, который существует до тех пор, пока он
не станет ненужным. В частности, бывает удобно создать объект, который
можно использовать после возврата из функции, где он был создан.
Подобные объекты создает операция new, а операция delete используется
для их уничтожения в дальнейшем. Про объекты, созданные операцией new,
говорят, что они размещаются в свободной памяти. Примерами таких
объектов являются узлы деревьев или элементы списка, которые входят
в структуры данных, размер которых на этапе трансляции неизвестен.
Давайте рассмотрим в качестве примера набросок транслятора, который
строится аналогично программе калькулятора. Функции синтаксического
анализа создают из представлений выражений дерево, которое будет
в дальнейшем использоваться для генерации кода. Например:
struct enode {
token_value oper;
enode* left;
enode* right;
};
enode* expr()
{
enode* left = term();
for(;;)
switch(curr_tok) {
case PLUS:
case MINUS:
get_token();
enode* n = new enode;
n->oper = curr_tok;
n->left = left;
n->right = term();
left = n;
break;
default:
return left;
}
}
Генератор кода может использовать дерево выражений, например так:
void generate(enode* n)
{
switch (n->oper) {
case PLUS:
// соответствующая генерация
delete n;
}
}
Объект, созданный с помощью операции new, существует, до тех пор,
пока он не будет явно уничтожен операцией delete. После этого
память, которую он занимал, вновь может использоваться new. Обычно нет
никакого "сборщика мусора", ищущего объекты, на которые никто
не ссылается, и предоставляющего занимаемую ими память операции new для
повторного использования. Операндом delete может быть
только указатель, который возвращает операция new, или нуль.
Применение delete к нулю не приводит ни к каким действиям.
Операция new может также создавать массивы объектов, например:
char* save_string(const char* p)
{
char* s = new char[strlen(p)+1];
strcpy(s,p);
return s;
}
Отметим, что для перераспределения памяти, отведенной операцией new,
операция delete должна уметь определять размер размещенного объекта.
Например:
int main(int argc, char* argv[])
{
if (argc < 2) exit(1);
char* p = save_string(arg[1]);
delete[] p;
}
Чтобы добиться этого, приходится под объект, размещаемый стандартной
операцией new, отводить немного больше памяти, чем под статический
(обычно, больше на одно слово). Простой оператор delete уничтожает
отдельные объекты, а операция delete[] используется для уничтожения
массивов.
Операции со свободной памятью реализуются функциями ($$R.5.3.3-4):
void* operator new(size_t);
void operator delete(void*);
Здесь size_t - беззнаковый целочисленный тип, определенный в <stddef.h>.
Стандартная реализация функции operator new() не инициализирует
предоставляемую память.
Что случится, когда операция new не сможет больше найти свободной
памяти для размещения? Поскольку даже виртуальная память небесконечна,
такое время от времени происходит. Так, запрос вида:
char* p = new char [100000000];
обычно не проходит нормально. Когда операция new не может выполнить
запрос, она вызывает функцию, которая была задана как параметр
при обращении к функции set_new_handler() из <new.h>. Например,
в следующей программе:
#include <iostream.h>
#include <new.h>
#include <stdlib.h>
void out_of_store()
{
cerr << "operator new failed: out of store\n";
exit(1);
}
int main()
{
set_new_handler(&out_of_store);
char* p = new char[100000000];
cout << "done, p = " << long(p) << '\n';
}
скорее всего, будет напечатано не "done", а сообщение:
operator new failed: out of store
// операция new не прошла: нет памяти
С помощью функции new_handler можно сделать нечто более сложное,
чем просто завершить программу. Если известен алгоритм операций new и
delete (например, потому, что пользователь определил свои функции
operator new и operator delete), то обработчик new_handler может
попытаться найти свободную память для new. Другими словами,
пользователь может написать свой "сборщик мусора", тем самым сделав
вызов операции delete необязательным. Однако такая задача,
безусловно, не под силу новичку.
По традиции операция new просто возвращает указатель 0, если не
удалось найти достаточно свободной памяти. Реакция же на это
new_handler не была установлена. Например, следующая программа:
#include <stream.h>
main()
{
char* p = new char[100000000];
cout << "done, p = " << long(p) << '\n';
}
выдаст
done, p = 0
Память не выделена, и вам сделано предупреждение! Отметим, что, задав
реакцию на такую ситуацию в функции new_handler, пользователь берет
на себя проверку: исчерпана ли свободная память. Она должна выполняться
при каждом обращении в программе к new (если только пользователь
не определил собственные функции для размещения объектов
пользовательских типов; см.$$R.5.5.6).
3.3 Сводка операторов
Полное и последовательное описание операторов С++ содержится в
$$R.6. Советуем ознакомиться с этим разделом. Здесь же дается
сводка операторов и несколько примеров.
------------------------------------------------------------------
Синтаксис операторов
------------------------------------------------------------------
оператор:
описание
{ список-операторов opt }
выражение opt ;
if ( выражение ) оператор
if ( выражение ) оператор else оператор
switch ( выражение ) оператор
while ( выражение ) оператор
do оператор while ( выражение )
for (начальный-оператор-for выражение opt; выражение opt) оператор
case выражение-константа : оператор
default : оператор
break ;
continue ;
return выражение opt ;
goto идентификатор ;
идентификатор : оператор
список-операторов:
оператор
список-операторов оператор
начальный-оператор-for:
описание
выражение opt ;
----------------------------------------------------------------------
Обратите внимание, что описание является оператором, но нет операторов
присваивания или вызова функции (они относятся к выражениям).
3.3.1 Выбирающие операторы
Значение можно проверить с помощью операторов if или switch:
if ( выражение ) оператор
if ( выражение ) оператор else оператор
switch ( выражение ) оператор
В языке С++ среди основных типов нет отдельного булевского (тип
со значениями истина, ложь). Все операции отношений:
== != < > <= >=
дают в результате целое 1, если отношение выполняется, и 0 в противном
случае. Обычно определяют константы TRUE как 1 и FALSE как 0.
В операторе if, если выражение имеет ненулевое значение,
выполняется первый оператор, а иначе выполняется второй (если
он указан). Таким образом, в качестве условия допускается любое выражение
типа целое или указатель. Пусть a целое, тогда
if (a) // ...
эквивалентно
if (a != 0) ...
Логические операции
&& || !
обычно используются в условиях. В операциях && и || второй операнд
не вычисляется, если результат определяется значением первого
операнда. Например, в выражении
if (p && l<p->count) // ...
сначала проверяется значение p, и только если оно не равно нулю, то
проверяется отношение l<p->count.
Некоторые простые операторы if удобно заменять выражениями
условия. Например, вместо оператора
if (a <= b)
max = b;
else
max = a;
лучше использовать выражение
max = (a<=b) ? b : a;
Условие в выражении условия не обязательно окружать скобками, но
если их использовать, то выражение становится понятнее.
Простой переключатель (switch) можно записать с помощью
серии операторов if. Например,
switch (val) {
case 1:
f();
break;
case 2:
g();
break;
default:
h();
break;
}
можно эквивалентно задать так:
if (val == 1)
f();
else if (val == 2)
g();
else
h();
Смысл обеих конструкций совпадает, но все же первая предпочтительнее,
поскольку в ней нагляднее показана суть операции: проверка на
совпадение значения val со значением из множества констант. Поэтому в
нетривиальных случаях запись, использующая переключатель, понятнее.
Нужно позаботиться о каком-то завершении оператора, указанного
в варианте переключателя, если только вы не хотите, чтобы стали
выполняться операторы из следующего варианта. Например,
переключатель
switch (val) { // возможна ошибка
case 1:
cout << "case 1\n";
case 2:
cout << "case 2\n";
default:
cout << "default: case not found\n";
}
при val==1 напечатает к большому удивлению непосвященных:
case 1
case 2
default: case not found
Имеет смысл отметить в комментариях те редкие случаи, когда стандартный
переход на следующий вариант оставлен намеренно. Тогда этот переход
во всех остальных случаях можно смело считать ошибкой. Для
завершения оператора в варианте чаще всего используется break, но
иногда используются return и даже goto. Приведем пример:
switch (val) { // возможна ошибка
case 0:
cout << "case 0\n";
case1:
case 1:
cout << "case 1\n";
return;
case 2:
cout << "case 2\n";
goto case1;
default:
cout << "default: case not found\n";
return;
}
Здесь при значении val равном 2 мы получим:
case 2
case 1
Отметим, что метку варианта нельзя использовать в операторе goto:
goto case 2; // синтаксическая ошибка