Читая учебники по программированию, убеждаюсь в том, как сильно усложняется восприятие материала, если автор подчиняет себя концепциям и инструментам, забывая о том, насколько важен контекст, в котором раскрывается материал. И, в то же время, насколько естественно и легко приходит понимание, когда ощущаешь реальное удобство от применения нового инструмента.
На мой взгляд, указатели на функции является как раз таким примером. Дело в том, что синтаксис объявления и использования указателей на функции не слишком очевиден, особенно для не очень опытных программистов, и, если сразу «сыпать» деталями и синтаксическими возможностями, то за деревьями не будет видно леса. В этом посте я хочу поделиться своим видением указателей на функции и привести простой пример, который, смею надеяться, порадует своей легкостью и убьет еще одного зайца — продемонстрирует страшного монстра под названием «полиморфный вызов».
Итак, пусть у нас есть массив строк (хотя никто не мешает ему быть и каким-нибудь другим массивом), который нужно сортировать. Именно сортировать, а не отсортировать. То есть планируем мы это делать регулярно и разными способами.
Пусть нам требуется иметь возможность сортировки по алфавиту (вверх/вниз) и по длине строки (вверх/вниз). В других случаях это может быть по росту, по весу, по населению, по зарплате. Конечно, мы могли бы реализовать какой-нибудь метод сортировки и оттиражировать его с изменением способа сравнения. Но, как мы понимаем, возникает дублирование кода со всеми вытекающими из него последствиями.
А как же иначе быть, если мы не можем передать функцию в качестве аргумента в функцию сортировки? Или, все-таки, можем? Здесь и приходят на помощь указатели на функции. Это как раз то средство, которое позволяет работать с функциями так же, как и с переменными. Ну, или почти так же.
Итак, вот он указатель на функцию. Встречайте:
Не пытайтесь разобраться в синтаксисе. Просто знайте, что после этой команды мы можем использовать тип CompareFunction, чтобы объявлять переменные-функции, правда с фиксированной сигнатурой. В нашем случае это должны быть логические функции с двумя целочисленными аргументами. Как вы могли догадаться, это будут различные способы сравнения. Вот и они:
Теперь помещаем их в массив:
А затем создаем функцию сортировки:
А что же там выделено жирным? Это и есть полиморфный вызов: переменной compare можно подсунуть любую функцию сравнения, и вызываемая функция будет определяться динамически, обеспечивая требуемый способ сортировки.
И, наконец, нужен какой-нибудь простенький user interface, чтобы все протестировать.
Теперь все прекрасно. Один программист штампует логические функции, второй — оптимизирует алгоритм сортировки, а третий — работает над юзабельностью пользовательского интерфейса. И никто никому не мешает — даже дублирование кода.
P.S. Функция printCities() виртуальная, ее реализация выпадает на долю читателя.
На мой взгляд, указатели на функции является как раз таким примером. Дело в том, что синтаксис объявления и использования указателей на функции не слишком очевиден, особенно для не очень опытных программистов, и, если сразу «сыпать» деталями и синтаксическими возможностями, то за деревьями не будет видно леса. В этом посте я хочу поделиться своим видением указателей на функции и привести простой пример, который, смею надеяться, порадует своей легкостью и убьет еще одного зайца — продемонстрирует страшного монстра под названием «полиморфный вызов».
Итак, пусть у нас есть массив строк (хотя никто не мешает ему быть и каким-нибудь другим массивом), который нужно сортировать. Именно сортировать, а не отсортировать. То есть планируем мы это делать регулярно и разными способами.
const int n = 15; string cities[n] = { "Красноярск", "Москва", "Новосибирск", "Лондон", "Токио", "Пекин", "Волгоград", "Минск", "Владивосток", "Париж", "Манчестер", "Вашингтон", "Якутск", "Екатеринбург", "Омск" };
Пусть нам требуется иметь возможность сортировки по алфавиту (вверх/вниз) и по длине строки (вверх/вниз). В других случаях это может быть по росту, по весу, по населению, по зарплате. Конечно, мы могли бы реализовать какой-нибудь метод сортировки и оттиражировать его с изменением способа сравнения. Но, как мы понимаем, возникает дублирование кода со всеми вытекающими из него последствиями.
А как же иначе быть, если мы не можем передать функцию в качестве аргумента в функцию сортировки? Или, все-таки, можем? Здесь и приходят на помощь указатели на функции. Это как раз то средство, которое позволяет работать с функциями так же, как и с переменными. Ну, или почти так же.
Итак, вот он указатель на функцию. Встречайте:
typedef bool(*CompareFunction) (int i, int j);
Не пытайтесь разобраться в синтаксисе. Просто знайте, что после этой команды мы можем использовать тип CompareFunction, чтобы объявлять переменные-функции, правда с фиксированной сигнатурой. В нашем случае это должны быть логические функции с двумя целочисленными аргументами. Как вы могли догадаться, это будут различные способы сравнения. Вот и они:
bool compareUp(int i, int j) { return cities[i] < cities[j]; } bool compareDown(int i, int j) { return cities[i] > cities[j]; } bool compareLengthUp(int i, int j) { return cities[i].length() < cities[j].length(); } bool compareLengthDown(int i, int j) { return cities[i].length() > cities[j].length(); }
Теперь помещаем их в массив:
CompareFunction compares[] = {compareUp, compareDown, compareLengthUp, compareLengthDown};
А затем создаем функцию сортировки:
void sortCities(CompareFunction compare) { for (int i = 0; i < n - 1; i++) { int j_min = i; for (int j = i+1; j < n; j++) { if (<b>compare(j, j_min)</b>) { j_min = j; } } string temp = cities[i]; cities[i] = cities[j_min]; cities[j_min] = temp; } }
А что же там выделено жирным? Это и есть полиморфный вызов: переменной compare можно подсунуть любую функцию сравнения, и вызываемая функция будет определяться динамически, обеспечивая требуемый способ сортировки.
И, наконец, нужен какой-нибудь простенький user interface, чтобы все протестировать.
int _tmain(int argc, _TCHAR* argv[]) { setlocale(LC_ALL, "Russian"); for (;;) { int choice; cout << "Ваш выбор: "; cin >> choice; sortCities(compares[choice]); printCities(); } system("pause"); return 0; }
Теперь все прекрасно. Один программист штампует логические функции, второй — оптимизирует алгоритм сортировки, а третий — работает над юзабельностью пользовательского интерфейса. И никто никому не мешает — даже дублирование кода.
P.S. Функция printCities() виртуальная, ее реализация выпадает на долю читателя.
