|
|||||||
АвтоАвтоматизацияАрхитектураАстрономияАудитБиологияБухгалтерияВоенное делоГенетикаГеографияГеологияГосударствоДомДругоеЖурналистика и СМИИзобретательствоИностранные языкиИнформатикаИскусствоИсторияКомпьютерыКулинарияКультураЛексикологияЛитератураЛогикаМаркетингМатематикаМашиностроениеМедицинаМенеджментМеталлы и СваркаМеханикаМузыкаНаселениеОбразованиеОхрана безопасности жизниОхрана ТрудаПедагогикаПолитикаПравоПриборостроениеПрограммированиеПроизводствоПромышленностьПсихологияРадиоРегилияСвязьСоциологияСпортСтандартизацияСтроительствоТехнологииТорговляТуризмФизикаФизиологияФилософияФинансыХимияХозяйствоЦеннообразованиеЧерчениеЭкологияЭконометрикаЭкономикаЭлектроникаЮриспунденкция |
Шаблоны функций. Шаблон простой функции. Шаблон функции с несколькими аргументамиШаблоны функций Допустим, вам нужно написать функцию вычисления модуля чисел. Из школь- ного курса алгебры вы, конечно, знаете, что модуль — это абсолютное значение числа, то есть число без знака. Напоминаем: модуль числа 3 равен 3, модуль чис- ла -3 равен 3. Скорее всего, функция вычисления модуля будет использоваться с каким- либо одним типом данных: int abs(int n) //вычисление модулей целых чисел { return (n<0)? -n: n; //если отрицательное, вернуть -n } Определенная выше функция берет аргумент типа int и возвращает резуль- тат того же типа. Но теперь представьте, что понадобилось найти модуль числа типа long. Придется писать еще одну функцию: _640________________________________________________________________ long abs(long n) //вычисление модулей чисел типа long { return (n<0)? -n: n; } Да, теперь еще одну для float: float abs(float n) //модуль целых чисел { return (n<0)? -n: n; //если отрицательное, вернуть -n } Тело функций, как видите, ничем не отличается. И все же они совершенно разные, поскольку обрабатывают аргументы и возвращают значения разных типов. Они могут быть перегружены и иметь одинаковые имена, это правда, но все равно для каждой из них нужно писать отдельное определение. (Что касается языка С, в котором нет понятия перегрузки, нельзя использовать даже одинаковые имена для обозначения функций разных типов. Там это приведет к созданию целого кла- на функций с похожими названиями, таких, как abs(), fabs(), fabs1(), labs() и cabs().) Многократное переписывание этих функций-близнецов очень утомляет, за- трудняет читабельность листинга, зато делает его увесистым. Можно показать неопытному заказчику тома исходных кодов, демонстрируя свою напряженную работу над проектом. А если где-то в алгоритме попадется ошибка, придется ис- правлять ее в теле каждой функции. Причем некорректное исправление ошибки приведет к несостоятельности программы. Было бы здорово, если бы был какой-то способ написать эту функцию всего один раз и заставить ее работать с различными типами данных, возвращая, со- ответственно, результаты разного типа. Шаблон простой функции Первый пример в этой главе показывает, как пишется шаблон функции, вычис- ляющей модуль числа. Шаблон работает со всеми базовыми числовыми типами. В программе сначала определяется шаблонная версия abs(), а затем в main() про- изводится проверка правильности ее работы. Листинг 14.1. Программа TEMPABS // tempabs.cpp // Шаблон функции вычисления модуля числа #include <iostream> using namespace std; //--------------------------------------------------------- template <class T> //Шаблон функции! T abs(T n) { return (n < 0)? -n: n; } //--------------------------------------------------------- int main() { int int1 = 5; int int2 = -6; long lon1 = 70000L; long lon2 = -80000L; double dub1 = 9.95; double dub2 = -10.15; //осуществления вызовов cout << "\nabs(" << int1 << ")=" << abs(int1); //abs(int) cout << "\nabs(" << int2 << ")=" << abs(int2); //abs(int) cout << "\nabs(" << lon1 << ")=" << abs(lon1); //abs(long) cout << "\nabs(" << lon2 << ")=" << abs(lon2); //abs(long) cout << "\nabs(" << dub1 << ")=" << abs(dub1); //abs(double) cout << "\nabs(" << dub2 << ")=" << abs(dub2); //abs(double) cout << endl; return 0; }
Результаты работы программы: abs(5)=5 abs(-6)=6 abs(70000)=70000 abs(-80000)=80000 abs(9.95)=9.95 abs(-10.15)=10.15 Итак, теперь функция abs() может работать со всеми тремя типами данных (int, long, double). Типы определяются функцией при передаче аргумента. Она будет работать и с другими базовыми числовыми типами, да даже и с пользо- вательскими, лишь бы был надлежащим образом перегружен оператор «мень- ше» (<) и унарный оператор «-». Вот спецификация функции abs() для работы с несколькими типами данных: template <class Т> //шаблон функции T abs (Т n) { return (n<0)? -n: n; } Вся конструкция, начиная от ключевого слова template в начале первой строки и включая определение функции, называется шаблоном функции. Каким образом достигается такая потрясающая гибкость использования функции? Синтаксис шаблона функции Краеугольным камнем концепции шаблонов функции является представление использующегося функцией типа не в виде какого-либо специфического (напри-мер, int), а с помощью названия, вместо которого может быть подставлен любой тип. В приведенном примере таким именем является Т (никакого сакрального смысла именно в таком названии нет. На его месте может все что угодно, например anyType или YooHoo). Ключевое слово template сообщает компилятору о том, что «сейчас будет море крови»: мы определяем шаблон функции. Ключевое слово class, заключенное в угловые скобки, можно с тем же успехом заменить на type. Как вы уже видели, можно определять собственные типы, используя классы, поэто- му разницы между типами и классами в известном смысле нет совсем. Переменная, следующая за словом class (Т в нашем примере), называется аргументом шаблона. Внутри всего определения шаблона любой конкретный тип данных, такой, как int, заменяет аргумент Т. В abs() это имя встречается лишь дважды в первой строке в качестве типа аргумента и одновременно в качестве типа функции. В более сложном случае оно могло бы встретиться много раз, в том числе в теле функции. Действия компилятора Что компилятор делает, увидев ключевое слово template и следующее за ним определения? Правильно, почти ничего или очень мало. Дело в том, что сам по себе шаблон не вызывает генерацию компилятором какого-либо кода. Да и не может он этого сделать, поскольку еще не знает, с каким типом дан- ных будет работать данная функция. Он разве что запоминает шаблон для буду-щего использования. Генерации кода не происходит до тех пор, пока функция не будет реально вызвана в ходе выполнения программы. В примере TEMPABS это происходит в конструкциях типа abs(int) в выражении cout << "\nabs(" << int1 << ")=" << abs(int1); Когда компилятор видит такой вызов функции, он знает, что нужно исполь- зовать тип int, поскольку это тип аргумента int1, переданного в шаблон. Поэто- му он генерирует код abs для типа int, подставляя его везде вместо Т. Это называется реализацией шаблона функции; при этом каждый реализованный шаблон функции называется шаблонной функцией1 Компилятор также генерирует вызов реализованной функции и вставляет его туда, где находится abs(int1). Выражение abs(lon1) приводит к тому, что компилятором генерируется версия abs(int), работающая с типом long, и ее вызов. То же самое касается типа float. Конечно, компилятор достаточно умен для того, чтобы создавать для каждого типа данных только одну копию abs(). Таким образом, несмотря на два вызова int-версии функции, в исполняемом коде она будет рисутствовать только один раз. Шаблоны функций с несколькими аргументами Взглянем на другой пример шаблона функции. У него три аргумента, два из которых шаблонные, а один — базового типа. Функция предназначена для поиса в массиве заданного числа. Она возвращает индекс найденного значения или -1 в случае его отсутствия в массиве. Аргументами являются указатель на массив, значение, которое нужно искать, а также размер массива. В main() мы определяем четыре различных массива различных типов и четыре значения, которые нужно найти. Тип char в данном примере воспринимается как число. Для каждого массива вызывается шаблонная функция. Листинг 14.2. Программа TEMPFIND // tempfind.cpp // Шаблон функции поиска в массиве #include <iostream> using namespace std; // функция возвращает индекс или –1 при отсутствии //совпадения template <class atype> int find(atype* array, atype value, int size) { for(int j=0; j<size; j++) if(array[j]==value) return j; return -1; } char chrArr[] = {1, 3, 5, 9, 11, 13}; //массив char ch = 5; //искомое значение int intArr[] = {1, 3, 5, 9, 11, 13}; int in = 6; long lonArr[] = {1L, 3L, 5L, 9L, 11L, 13L}; long lo = 11L; double dubArr[] = {1.0, 3.0, 5.0, 9.0, 11.0, 13.0}; double db = 4.0;
int main() { cout << "\n 5 в chrArray: индекс=" << find(chrArr, ch, 6); cout << "\n 6 в intArray: индекс=" << find(intArr, in, 6); cout << "\n11 в lonArray: индекс=" << find(lonArr, lo, 6); cout << "\n 4 в dubArray: индекс=" << find(dubArr, db, 6); cout << endl; return 0; }
Здесь, как видите, мы называем шаблонный аргумент именем atype. Оно по- является два раза в аргументах: как тип указателя на массив и как тип искомого значения. Третий аргумент — размер массива — всегда типа int. Это не шаблон- ный аргумент. Вот результаты работы программы: 5 в chrArray: индекс=2 6 в intArray: индекс=-1 11 в lonArray: индекс=4 4 в dubArray: индекс=-1 Компилятор генерирует четыре разных варианта функции, по одному на каждый тип. Функция находит число 5 в ячейке массива с индексом 2, не находит число 6 в массиве вообще и т. д. Аргументы шаблона должны быть согласованы При вызове шаблонной функции все экземпляры данного аргумента шаблона должны быть строго одного типа. Например, в find() есть массив типа int, соответственно, искомое значение должно быть того же типа. Нельзя делать так: int intarray[]={1,3,5,7}; //массив int float fl = 5.0; //значение float int value = find(intarray, f1, 4);//ax. ox! Компилятору нужно, чтобы все экземпляры atype были одного типа. Он может генерировать код функции Find(int*, int, int); но не станет заниматься такими глупостями: find(int*, float, int); потому что первый и второй аргумент должны быть одного типа. А почему не макросы? Старосветские программисты, пишущие на C, снова удивятся, почему же мы не спользуем макросы для создания функций, работающих с разными типами дан- ных? Например, функция abs() могла бы быть определена так: #define abs(n) ((n<0)? (-n): (n)) Это возымело бы тот же эффект, что и от шаблона класса в TEMPABS, потому что здесь используеся простое замещение одного текста на другой, а значит, можно указывать любые типы данных. Тем не менее, как уже отмечалось ранее, макросы редко используются в C++. С ними возникают некоторые проблемы. Одна из них заключается в том, что макросы не осуществляют проверку типов. Может быть несколько переменных макроса, которые обязаны быть одного типа, но компилятор не проверяет это условие. Еще одна проблема состоит в том, что не указан тип возвращаемого значения, поэтому компилятор, опять же, не может проверить корректность присваивания. В любом случае, макросы ограничены функциями, которые можно выразить с помощью всего одного выражения. Да есть еще и другие проблемы с макросами, которые привели к выходу их из моды при программировании на C++. В целом, лучше избегать их использования.
Поиск по сайту: |
Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Студалл.Орг (0.011 сек.) |