Читая учебники по программированию, убеждаюсь в том, как сильно усложняется восприятие материала, если автор подчиняет себя концепциям и инструментам, забывая о том, насколько важен контекст, в котором раскрывается материал. И, в то же время, насколько естественно и легко приходит понимание, когда ощущаешь реальное удобство от применения нового инструмента.
На мой взгляд, указатели на функции является как раз таким примером. Дело в том, что синтаксис объявления и использования указателей на функции не слишком очевиден, особенно для не очень опытных программистов, и, если сразу «сыпать» деталями и синтаксическими возможностями, то за деревьями не будет видно леса. В этом посте я хочу поделиться своим видением указателей на функции и привести простой пример, который, смею надеяться, порадует своей легкостью и убьет еще одного зайца — продемонстрирует страшного монстра под названием «полиморфный вызов».
Итак, пусть у нас есть массив строк (хотя никто не мешает ему быть и каким-нибудь другим массивом), который нужно сортировать. Именно сортировать, а не отсортировать. То есть планируем мы это делать регулярно и разными способами.
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() виртуальная, ее реализация выпадает на долю читателя.
Автор: pestunov