Категории
Самые читаемые
Лучшие книги » Компьютеры и Интернет » Программирование » Язык программирования C++. Пятое издание - Стенли Липпман

Язык программирования C++. Пятое издание - Стенли Липпман

Читать онлайн Язык программирования C++. Пятое издание - Стенли Липпман

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 232 233 234 235 236 237 238 239 240 ... 297
Перейти на страницу:

Функции с переменным количеством аргументов зачастую рекурсивны (см. раздел 6.3.2). Первый вызов обрабатывает первый аргумент в пакете и вызывает себя для остальных аргументов. Новая функция print() будет работать таким же образом — каждый вызов выводит свой второй аргумент в поток, обозначенный первым аргументом. Для остановки рекурсии следует определить также обычную функцию print(), которая получает поток и объект:

// Функция для завершения рекурсии и вывода последнего элемента

// ее следует объявить перед определением версией print() с переменным

// количеством аргументов

template<typename Т>

ostream &print(ostream &os, const T &t) {

 return os << t; // нет разделителя после последнего элемента в пакете

}

// эта версия print() будет вызвана для всех элементов в пакете, кроме

// последнего

template <typename Т, typename... Args>

ostream &print(ostream &os, const T &t, const Args&... rest) {

 os << t << ", "; // выводит первый аргумент

 return print(os, rest...); // рекурсивный вызов; вывод других

                            // аргументов

}

Первая версия функции print() останавливает рекурсию и выводит последний аргумент в начальном вызове функции print(). Вторая версия, с переменным количеством аргументов, выводит аргумент, связанный с t, и вызывает себя для вывода остальных значений в пакете параметров функции.

Ключевая часть — вызов функции print() в функции с переменным количеством аргументов:

return print(os, rest...); // рекурсивный вызов; вывод других

// аргументов

Версия функции print() с переменным количеством аргументов получает три параметра: ostream&, const T& и пакет параметров. Но в этом вызове передаются только два аргумента. В результате первый аргумент в пакете rest привязывается к t. Остальные аргументы в пакете rest формируют пакет параметров для следующего вызова функции print(). Таким образом, при каждом вызове первый аргумент удаляется из пакета и становится аргументом, связанным с t. Соответственно, получаем:

print(cout, i, s, 42); // два параметра в пакете

Рекурсия выполнится следующим образом:

Вызов t rest... print(cout, i, s, 42) i s, 42 print(cout, s, 42) s 42

Вызов print(cout, 42) вызывает обычную версию функции print().

Первые два вызова могут соответствовать только версии функции print() с переменным количеством аргументов, поскольку обычная версия не является подходящей. Эти вызовы передают четыре и три аргумента соответственно, а обычная функция print() получает только два аргумента.

Для последнего вызова в рекурсии, print(cout, 42), подходят обе версии функции print(). Этот вызов передает два аргумента, и типом первого являются ostream&. Таким образом, подходящей является обычная версия функции print().

Версия с переменным количеством аргументов также является подходящей. В отличие от обычного аргумента, пакет параметров может быть пустым. Следовательно, экземпляр версии функции print() с переменным количеством аргументов может быть создан только с двумя параметрами: один — для параметра ostream& и другой — для параметра const T&.

Обе функции обеспечивают одинаково хорошее соответствие для вызова. Однако нешаблонная версия с переменным количеством аргументов более специализирована, чем шаблонная с переменным количеством аргументов. Поэтому выбирается версия без переменного количества аргументов (см. раздел 16.3).

Объявление версии функции print() с постоянным количеством аргументов должно быть в области видимости, когда определяется версия с переменным количеством аргументов. В противном случае функция с переменным количеством аргументов будет рекурсивно вызывать себя бесконечно.

Упражнения раздела 16.4.1

Упражнение 16.53. Напишите собственные версии функций print() и проверьте их, выводя один, два и пять аргументов, у каждых из которых должны быть разные типы.

Упражнение 16.54. Что происходит при вызове функции print() для типа, не имеющего оператора <<?

Упражнение 16.55. Объясните, как выполнилась бы версия функции print() с переменным количеством аргументов, если бы обычная версия функции print() была объявлена после определения версии с переменным количеством аргументов.

16.4.2. Развертывание пакета

Кроме выяснения размера, единственное, что можно еще сделать с пакетом параметров, — это развернуть (pack expansion) его. При развертывании пакета предоставляется схема (pattern), используемая для каждого развернутого элемента. Развертывание пакета разделяет его на элементы с применением схемы к каждому из них. Для запуска развертывания справа от схемы помещают многоточие (...).

Например, функция print() содержит два развертывания:

template <typename Т, typename... Args> ostream &

print(ostream &os, const T &t, const Args&... rest) // развертывание

                                                    // Args

{

 os << t << ", ";

 return print(os, rest...); // развертывание rest

}

В первом случае развертывание пакета параметров шаблона создает список параметров функции print(). Второй случай развертывания находится в вызове функции print(). Эта схема создает список аргументов для вызова.

Развертывание пакета Args применяет схему const Args& к каждому элементу в пакете параметров шаблона Args. Результатом этой схемы будет разделенный запятыми список из любого количества типов параметров в формате const тип&. Например:

print(cout, i, s, 42); // два параметра в пакете

Типы последних двух аргументов, наряду со схемой, определяют типы замыкающих параметров. Этот вызов создает следующий экземпляр:

ostream&

print(ostream&, const int&, const strings, const int&);

Второе развертывание происходит в рекурсивном вызове функции print(). В данном случае схема — это имя пакета параметров функции (т.е. rest). Эта схема развертывается в разделяемый запятыми список элементов пакета. Таким образом, этот вызов эквивалентен следующему:

print(os, s, 42);

Концепция развертывания пакета

Развертывание пакета параметров функции print() только разворачивало пакет на его составные части. При развертывании пакета параметров функции возможны и более сложные схемы. Например, можно было бы написать вторую функцию с переменным количеством аргументов, которая вызывает функцию debug_rep() (см. раздел 16.3) для каждого из своих аргументов, а затем вызывает функцию print(), чтобы вывести полученные строки:

// вызвать debug_rep() для каждого аргумента в вызове print()

template <typename... Args>

ostream &errorMsg(ostream &os, const Args&... rest) {

 // print(os, debug_rep(a1), debug_rep(a2), ..., debug_rep(an)

 return print(os, debug_rep(rest)...);

}

Вызов функции print() использует схему debug_rep(rest). Эта схема означает, что функцию debug_rep() следует вызвать для каждого элемента в пакете параметров функции rest. Получившийся развернутый пакет будет разделяемым запятыми списком вызовов функции debug_rep(). Таким образом, вызов

errorMsg(cerr, fcnName, code.num(), otherData, "other", item);

выполняется, как будто было написано:

print(cerr, debug_rep(fcnName), debug_rep(code.num()),

            debug_rep(otherData), debug_rep("otherData"),

            debug_rep(item));

Следующая схема, напротив, не была бы откомпилирована:

// передает пакет debug_rep(); print(os, debug_rep(a1, a2, an))

print(os, debug_rep(rest...)); // ошибка: нет функции, соответствующей

                               // вызову

Проблема здесь в том, что пакет rest развернут в вызове функции debug_rep(). Этот вызов выполнился бы так, как будто было написано:

print(cerr, debug_rep(fcnName, code.num(),

            otherData, "otherData", item));

1 ... 232 233 234 235 236 237 238 239 240 ... 297
Перейти на страницу:
На этой странице вы можете бесплатно скачать Язык программирования C++. Пятое издание - Стенли Липпман торрент бесплатно.
Комментарии