
- •Содержание
- •1. Глава Первая 14
- •2. Глава Вторая 43
- •2.1. Описания 43
- •3. Глава Третья 78
- •4. Глава Четвертая 95
- •5. Глава Пятая. Классы 122
- •6. Глава Шестая. Перегрузка Операций 157
- •6.1. Введение 157
- •7. Глава Седьмая 182
- •7.1. Введение 182
- •8. Глава Восьмая. Потоки 206
- •8.1. Введение 207
- •Предисловие
- •Исторические замечания
- •Эффективность и структура
- •Правила правой руки
- •1.Глава Первая
- •1.1.Начало
- •1.1.1.Вывод на экран
- •1.1.2.Компиляция
- •1.1.3.Ввод
- •1.2.Комментарии
- •1.3.Типы и описания
- •1.3.1.Основные типы
- •1.3.2.Производные типы
- •1.4.Выражения и операторы
- •1.4.1.Выражения
- •1.4.6.Операторы switch
- •1.4.7.Оператор while
- •1.4.8.Оператор for
- •1.4.9.Описания
- •1.5.Функции
- •1.6.Структура программы
- •1.7.Классы
- •1.8.Перегрузка операций
- •1.9.Конструкторы
- •1.10.Векторы
- •1.11.Inline-подстановка
- •1.12.Производные классы
- •1.13.Еще об операциях
- •1.14.Друзья (friends)
- •1.15.Обобщенные векторы
- •1.16.Полиморфные Вектора
- •1.17.Виртуальные функции
- •2.Глава Вторая
- •2.1.Описания
- •2.1.1.Область видимости
- •2.1.2.Объекты и адреса (Lvalue)
- •2.1.3.Время жизни
- •2.2.Имена
- •2.3.Типы
- •2.3.1.Основные типы
- •2.3.2.Неявное преобразование типа
- •2.3.3.Производные типы
- •2.3.5.Указатели
- •2.3.6.Векторы
- •2.3.7.Указатели и векторы
- •2.3.8.Структуры
- •2.3.9.Эквивалентность типов
- •2.3.10.Ссылки
- •2.4.Константы
- •2.4.1.Целые константы
- •2.4.2.Константы с плавающей точкой
- •2.4.3.Символьные константы
- •2.4.4.Строки
- •2.4.5.Ноль
- •2.4.7.Перечисления
- •2.5.Экономия пространства
- •2.5.1.Поля
- •2.5.2.Объединения
- •3.Глава Третья
- •3.1.Краткая сводка операций
- •Сводка операций
- •3.1.1.Круглые скобки
- •3.1.2.Порядок вычисления
- •3.1.3.Увеличение и уменьшение
- •3.1.4.Побитовые логические операции
- •3.1.5.Преобразование типа
- •3.1.6.Свободная память
- •3.2.Сводка операторов
- •3.2.1.Проверки
- •3.3.Комментарии и выравнивание
- •4.Глава Четвертая
- •4.1.Введение
- •4.2. Компоновка
- •4.3.Заголовочные файлы
- •4.3.1.Один заголовочный файл
- •4.3.2.Множественные заголовочные файлы
- •4.3.3.Скрытие данных
- •4.4.Файлы как Модули
- •4.5.Как создать библиотеку
- •4.6.Функции
- •4.6.1.Описания функций
- •4.6.2.Определения функций
- •4.6.3.Передача Параметров
- •4.6.4.Возврат значения
- •4.6.5.Векторные параметры
- •4.6.6.Параметры по умолчанию
- •4.6.7.Перегрузка имен функций
- •4.6.8.Незаданное число параметров
- •4.6.9.Указатель на функцию
- •4.7. Макросы
- •5.Глава Пятая. Классы
- •5.1.Знакомство и краткий обзор
- •5.2.Классы и члены
- •5.2.1.Функции члены
- •5.2.2.Классы
- •5.2.3.Ссылки на себя
- •5.2.4.Инициализация
- •5.2.5.Очистка
- •5.3.Интерфейсы и реализации
- •5.3.1.Законченный класс
- •5.4.Друзья и объединения
- •5.4.1.Друзья
- •5.4.2.Уточнение имени члена
- •5.4.3.Вложенные классы
- •5.4.4.Статические члены
- •5.4.5.Структуры и объединения
- •5.5.Конструкторы и деструкторы
- •5.5.1.Предостережение
- •5.5.2.Статическая память
- •5.5.3.Свободная память
- •5.5.4.Объекты класса и члены
- •5.5.5.Векторы объектов класса
- •5.5.6.Небольшие объекты
- •5.5.7.Предостережение
- •5.5.8.Объекты переменного размера
- •6.Глава Шестая. Перегрузка Операций
- •6.1. Введение
- •6.2.Функции операции
- •6.2.1.Бинарные и унарные операции
- •6.2.2.Предопределенные значения операций
- •6.2.3.Операции и определяемые пользователем типы
- •6.3.Определяемое преобразование типа
- •6.3.1.Конструкторы
- •6.3.2.Операции преобразования
- •6.3.3.Неоднозначности
- •6.4.Константы
- •6.5.Большие объекты
- •6.6.Присваивание и инициализация
- •6.7.Индексирование
- •6.8.Вызов функции
- •6.9.Класс строка
- •6.10.Друзья и члены
- •6.11.Предостережение
- •7.Глава Седьмая
- •7.1.Введение
- •7.2.Производные классы
- •7.2.1.Построение производного класса
- •7.2.2.Функции члены
- •7.2.3.Видимость
- •7.2.4.Указатели
- •7.2.5.Иерархия типов
- •7.2.6.Конструкторы и деструкторы
- •7.2.7.Поля типа
- •7.2.8.Виртуальные функции
- •7.3.Альтернативные интерфейсы
- •7.3.1.Интерфейс
- •7.3.2.Реализация
- •7.3.3.Как этим пользоваться
- •7.3.4.Ограниченные интерфейсы
- •7.4.Добавление к классу
- •7.5.Неоднородные списки
- •8.Глава Восьмая. Потоки
- •8.1.Введение
- •8.2.Вывод
- •8.2.1.Вывод встроенных типов
- •8.2.2.Некоторые подробности разработки
- •8.2.3.Форматированный вывод
- •8.2.4.Виртуальная функция вывода
- •8.3.Файлы и потоки
- •8.3.1.Инициализация потоков вывода
- •8.3.2.Закрытие потоков вывода
- •8.3.3.Открытие файлов
- •8.3.4.Копирование потоков
- •8.4.Ввод
- •8.4.1.Ввод встроенных типов
- •8.4.2.Состояния потока
- •8.4.3.Ввод типов, определяемых пользователем
- •8.4.4.Инициализация потоков ввода
- •8.5.Работа со строками
- •8.6.Буферизация
- •8.7.Эффективность
4.6.6.Параметры по умолчанию
Часто в самом общем случае функции требуется больше параметров, чем в самом простом и более употребительном случае. Например, в библиотеке потоков есть функция hex(), порождающая строку с шестнадцатеричным представлением целого. Второй параметр используется для задания числа символов для представления первого параметра. Если число символов слишком мало для представления целого, происходит усечение, если оно слишком велико, то строка дополняется пробелами. Часто программист не заботится о числе символов, необходимых для представления целого, поскольку символов достаточно. Поэтому для нуля в качестве второго параметра определено значение «использовать столько символов, сколько нужно». Чтобы избежать засорения программы вызовами вроде hex(i,0), функция описывается так:
extern char* hex(long, int =0);
Инициализатор второго параметра является параметром по умолчанию. То есть, если в вызове дан только один параметр, в качестве второго используется параметр по умолчанию. Например:
cout << «**» << hex(31) << hex(32,3) << «**»;
интерпретируется как
cout << «**» << hex(31,0) << hex(32,3) << «**»;
и напечатает:
** 1f 20**
Параметр по умолчанию проходит проверку типа во время описания функции и вычисляется во время ее вызова. Задавать параметр по умолчанию возможно только для последних параметров, поэтому
int f(int, int =0, char* =0); // ok
int g(int =0, int =0, char*); // ошибка
int f(int =0, int, char* =0); // ошибка
Заметьте, что в этом контексте пробел между * и = является существенным (*= является операцией присваивания):
int nasty(char*=0); // синтаксическая ошибка
4.6.7.Перегрузка имен функций
Как правило, давать разным функциям разные имена - мысль хорошая, но когда некоторые функции выполняют одинаковую работу над объектами разных типов, может быть более удобно дать им одно и то же имя. Использование одного имени для различных действий над различными типами называется перегрузкой (overloading). Метод уже используется для основных операций C++: у сложения существует только одно имя, +, но его можно применять для сложения значений целых, плавающих и указательных типов. Эта идея легко расширяется на обработку операций, определенных пользователем, то есть, функций. Чтобы уберечь программиста от случайного повторного использования имени, имя может использоваться более, чем для одной функции, только если оно сперва описано как перегруженное. Например:
void print(int);
void print(char*);
Что касается компилятора, единственное общее, что имеют функции с одинаковым именем, это имя. Предположительно, они в каком-то смысле похожи, но в этом язык ни стесняет программиста, ни помогает ему. Таким образом, перегруженные имена функций - это главным образом удобство записи. Это удобство значительно в случае функций с общепринятыми именами вроде sqrt, print и open. Когда имя семантически значимо, как это имеет место для операций вроде +, * и << и в случае конструкторов, это удобство становится существенным. Когда вызывается перегруженная f(), компилятор должен понять, к какой из функций с именем f следует обратиться. Это делается путем сравнения типов фактических параметров с типами формальных параметров всех функций с именем f. Поиск функции, которую надо вызвать, осуществляется за три отдельных шага:
[1] искать функцию, соответствующую точно, и использовать ее, если она найдена;
[2] искать соответствующую, функцию используя встроенные преобразования, и использовать любую найденную функцию; и
[3] искать соответствующую функцию, используя преобразования, определенные пользователем, и, если множество преобразований единственно, использовать найденную функцию.
Например:
overload print(double), print(int);
void f();
{
print(1);
print(1.0);
}
Правило точного соответствия гарантирует, что f напечатает 1 как целое и 1.0 как число с плавающей точкой. Ноль, char или short точно соответствуют параметру int. Аналогично, float точно соответствует double.
К параметрам функций с перегруженными именами стандартные C++-правила преобразования применяются не полностью. Преобразования, могущие уничтожить информацию, не выполняются. Остаются int в long, int в double, ноль в long, ноль в double и преобразования указателей: ноль в указатель, ноль в void*, и указатель на производный класс в указатель на базовый класс.
Вот пример, в котором преобразование необходимо:
overload print(double), print(long);
void f(int a);
{
print(a);
}
Здесь a может быть напечатано или как double, или как long. Неоднозначность разрешается явным преобразованием типа (или print(long(a)) или print(double(a))).
При этих правилах можно гарантировать, что когда эффективность или точность вычислений для используемых типов существенно различаются, будет использоваться простейший алгоритм (функция). Например:
overload pow;
int pow(int, int);
double pow(double, double); // из
complex pow(double, complex); // из
complex pow(complex, int);
complex pow(complex, double);
complex pow(complex, complex);
Процесс поиска подходящей функции игнорирует unsigned и const.