На уроке №80 мы узнали, что указатель — это переменная, которая содержит адрес другой переменной. Указатели на функции аналогичны, за исключением того, что вместо обычных переменных, они указывают на функции!
Указатели на функции
Рассмотрим следующий фрагмент кода:
1 2 3 4 |
int boo() { return 7; } |
Идентификатор boo
— это имя функции. Но какой её тип? Функции имеют свой собственный l-value тип. В этом случае это тип функции, который возвращает целочисленное значение и не принимает никаких параметров. Подобно переменным, функции также имеют свой адрес в памяти.
Когда функция вызывается (с помощью оператора ()
), точка выполнения переходит к адресу вызываемой функции:
1 2 3 4 5 6 7 8 9 10 11 |
int boo() // код функции boo() находится в ячейке памяти 002B1050 { return 7; } int main() { boo(); // переходим к адресу 002B1050 return 0; } |
Одной из распространенных ошибок новичков является:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#include <iostream> int boo() // код функции boo() находится в ячейке памяти 002B1050 { return 7; } int main() { std::cout << boo; // мы хотим вызвать boo(), но вместо этого мы просто выводим boo! return 0; } |
Вместо вызова функции boo() и вывода возвращаемого значения мы, совершенно случайно, отправили указатель на функцию boo() непосредственно в std::cout. Что произойдет в этом случае?
Результат на моем компьютере:
002B1050
У вас может быть и другое значение, в зависимости от того, в какой тип данных ваш компилятор решит конвертировать указатель на функцию. Если ваш компьютер не вывел адрес функции, то вы можете заставить его это сделать, конвертируя boo
в указатель типа void и отправляя его на вывод:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#include <iostream> int boo() // код функции boo() находится в ячейке памяти 002B1050 { return 7; } int main() { std::cout << reinterpret_cast<void*>(boo); // указываем C++ конвертировать функцию boo() в указатель типа void return 0; } |
Так же, как можно объявить неконстантный указатель на обычную переменную, можно объявить и неконстантный указатель на функцию. Синтаксис создания неконстантного указателя на функцию, пожалуй, один из самых «уродливых» в языке C++:
1 2 |
// fcnPtr - это указатель на функцию, которая не принимает никаких аргументов и возвращает целочисленное значение int (*fcnPtr)(); |
В примере, приведенном выше, fcnPtr
— это указатель на функцию, которая не имеет параметров и возвращает целочисленное значение. fcnPtr
может указывать на любую другую функцию, соответствующую этому типу.
Скобки вокруг *fcnPtr
необходимы для соблюдения приоритета операций, в противном случае int *fcnPtr()
будет интерпретироваться как предварительное объявление функции fcnPtr
, которая не имеет параметров и возвращает указатель на целочисленное значение.
Для создания константного указателя на функцию используйте const после звёздочки:
1 |
int (*const fcnPtr)(); |
Если вы поместите const перед int, это будет означать, что функция, на которую указывает указатель, возвращает const int.
Присваивание функции указателю на функцию
Указатель на функцию может быть инициализирован функцией (и неконстантному указателю на функцию тоже можно присвоить функцию):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
int boo() { return 7; } int doo() { return 8; } int main() { int (*fcnPtr)() = boo; // fcnPtr указывает на функцию boo() fcnPtr = doo; // fcnPtr теперь указывает на функцию doo() return 0; } |
Одна из распространенных ошибок, которую совершают новички:
1 |
fcnPtr = doo(); |
Здесь мы фактически присваиваем возвращаемое значение из вызова функции doo() указателю fcnPtr
, чего мы не хотим делать. Мы хотим, чтобы fcnPtr
содержал адрес функции doo(), а не возвращаемое значение из doo(). Поэтому скобки здесь не нужны.
Обратите внимание, в указателя на функцию и самой функции должны совпадать тип, параметры и тип возвращаемого значения. Например:
1 2 3 4 5 6 7 8 9 10 11 |
// Прототипы функций int boo(); double doo(); int moo(int a); // Присваивание значений указателям на функции int (*fcnPtr1)() = boo; // ок int (*fcnPtr2)() = doo; // не ок: тип указателя и тип возврата функции не совпадают! double (*fcnPtr4)() = doo; // ок fcnPtr1 = moo; // не ок: fcnPtr1 не имеет параметров, но moo() имеет int (*fcnPtr3)(int) = moo; // ок |
В отличие от фундаментальных типов данных, язык C++ неявно конвертирует функцию в указатель на функцию, если это необходимо (поэтому вам не нужно использовать оператор адреса &
для получения адреса функции). Однако, язык C++ не будет неявно конвертировать указатель на функцию в указатель типа void или наоборот.
Вызов функции через указатель на функцию
Вы также можете использовать указатель на функцию для вызова самой функции. Есть два способа сделать это. Первый — через явное разыменование:
1 2 3 4 5 6 7 8 9 10 11 12 |
int boo(int a) { return a; } int main() { int (*fcnPtr)(int) = boo; // присваиваем функцию boo() указателю fcnPtr (*fcnPtr)(7); // вызываем функцию boo(7), используя fcnPtr return 0; } |
Второй — через неявное разыменование:
1 2 3 4 5 6 7 8 9 10 11 12 |
int boo(int a) { return a; } int main() { int (*fcnPtr)(int) = boo; // присваиваем функцию boo() указателю fcnPtr fcnPtr(7); // вызываем функцию boo(7), используя fcnPtr return 0; } |
Как вы можете видеть, способ неявного разыменования выглядит так же, как и обычный вызов функции, так как обычные имена функций являются указателями на функции!
Примечание: Параметры по умолчанию не будут работать с функциями, вызванными через указатели на функции. Параметры по умолчанию обрабатываются во время компиляции (т.е. вам нужно предоставить аргумент для параметра по умолчанию во время компиляции). Однако указатели на функции обрабатываются во время выполнения. Следовательно, параметры по умолчанию не могут обрабатываться при вызове функции через указатель на функцию. В этом случае вам нужно будет явно передать значения для параметров по умолчанию.
Передача функций в качестве аргументов другим функциям
Одна из самых полезных вещей, которую вы можете сделать с указателями на функции — это передать функцию в качестве аргумента другой функции. Функции, используемые в качестве аргументов для других функций, называются функциями обратного вызова.
Предположим, что вы пишете функцию для выполнения определенного задания (например, сортировки массива), но вы хотите, чтобы пользователь мог определить, каким образом выполнять эту сортировку (например, по возрастанию или по убыванию). Рассмотрим более подробно этот случай.
Все алгоритмы сортировки работают по одинаковой схеме: алгоритм выполняет итерацию по списку чисел, сравнивает пары чисел и меняет их местами, исходя из результатов этих сравнений. Следовательно, изменяя алгоритм сравнения чисел, мы можем изменить способ сортировки, не затрагивая остальные части кода.
Вот наша сортировка методом выбора, рассмотренная на соответствующем уроке:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#include <algorithm> // для std::swap() (используйте <utility>, если поддерживается C++11) void SelectionSort(int *array, int size) { // Перебираем каждый элемент массива for (int startIndex = 0; startIndex < size; ++startIndex) { // smallestIndex - это индекс наименьшего элемента, который мы обнаружили до этого момента int smallestIndex = startIndex; // Ищем наименьший элемент среди оставшихся в массиве (начинаем со startIndex+1) for (int currentIndex = startIndex + 1; currentIndex < size; ++currentIndex) { // Если текущий элемент меньше нашего предыдущего найденного наименьшего элемента, if (array[smallestIndex] > array[currentIndex]) // СРАВНЕНИЕ ВЫПОЛНЯЕТСЯ ЗДЕСЬ // то это наш новый наименьший элемент в этой итерации smallestIndex = currentIndex; } // Меняем местами наш стартовый элемент с найденным наименьшим элементом std::swap(array[startIndex], array[smallestIndex]); } } |
Давайте заменим сравнение чисел на функцию сравнения. Поскольку наша функция сравнения будет сравнивать два целых числа и возвращать логическое значение для указания того, следует ли выполнять замену, то она будет выглядеть следующим образом:
1 2 3 4 |
bool ascending(int a, int b) { return a > b; // условие, при котором меняются местами элементы массива } |
А вот уже сортировка методом выбора с функцией ascending() для сравнения чисел:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#include <algorithm> // для std::swap() (используйте <utility>, если поддерживается C++11) void SelectionSort(int *array, int size) { // Перебираем каждый элемент массива for (int startIndex = 0; startIndex < size; ++startIndex) { // smallestIndex - это индекс наименьшего элемента, который мы обнаружили до этого момента int smallestIndex = startIndex; // Ищем наименьший элемент среди оставшихся в массиве (начинаем со startIndex+1) for (int currentIndex = startIndex + 1; currentIndex < size; ++currentIndex) { // Если текущий элемент меньше нашего предыдущего найденного наименьшего элемента, if (ascending(array[smallestIndex], array[currentIndex])) // СРАВНЕНИЕ ВЫПОЛНЯЕТСЯ ЗДЕСЬ // то это наш новый наименьший элемент в этой итерации smallestIndex = currentIndex; } // Меняем местами наш стартовый элемент с найденным наименьшим элементом std::swap(array[startIndex], array[smallestIndex]); } } |
Теперь, чтобы позволить caller-у решить, каким образом будет выполняться сортировка, вместо использования нашей функции сравнения, мы разрешаем caller-у предоставить свою собственную функцию сравнения! Это делается с помощью указателя на функцию.
Поскольку функция сравнения caller-а будет сравнивать два целых числа и возвращать логическое значение, то указатель на эту функцию будет выглядеть следующим образом:
1 |
bool (*comparisonFcn)(int, int); |
Мы разрешаем caller-у передавать способ сортировки массива с помощью указателя на функцию в качестве третьего параметра в нашу функцию сортировки.
Вот готовый код сортировки методом выбора с выбором способа сортировки в caller-е (т.е. в функции main()):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
#include <iostream> #include <algorithm> // для std::swap() (используйте <utility>, если поддерживается C++11) // Обратите внимание, третьим параметром является пользовательский выбор выполнения сортировки void selectionSort(int *array, int size, bool (*comparisonFcn)(int, int)) { // Перебираем каждый элемент массива for (int startIndex = 0; startIndex < size; ++startIndex) { // bestIndex - это индекс наименьшего/наибольшего элемента, который мы обнаружили до этого момента int bestIndex = startIndex; // Ищем наименьший/наибольший элемент среди оставшихся в массиве (начинаем со startIndex+1) for (int currentIndex = startIndex + 1; currentIndex < size; ++currentIndex) { // Если текущий элемент меньше/больше нашего предыдущего найденного наименьшего/наибольшего элемента, if (comparisonFcn(array[bestIndex], array[currentIndex])) // СРАВНЕНИЕ ВЫПОЛНЯЕТСЯ ЗДЕСЬ // то это наш новый наименьший/наибольший элемент в этой итерации bestIndex = currentIndex; } // Меняем местами наш стартовый элемент с найденным наименьшим/наибольшим элементом std::swap(array[startIndex], array[bestIndex]); } } // Вот функция сравнения, которая выполняет сортировку в порядке возрастания (обратите внимание, это та же функция ascending(), что и в примере, приведенном выше) bool ascending(int a, int b) { return a > b; // меняем местами, если первый элемент больше второго } // Вот функция сравнения, которая выполняет сортировку в порядке убывания bool descending(int a, int b) { return a < b; // меняем местами, если второй элемент больше первого } // Эта функция выводит значения массива void printArray(int *array, int size) { for (int index=0; index < size; ++index) std::cout << array[index] << " "; std::cout << '\n'; } int main() { int array[8] = { 4, 8, 5, 6, 2, 3, 1, 7 }; // Сортируем массив в порядке убывания, используя функцию descending() selectionSort(array, 8, descending); printArray(array, 8); // Сортируем массив в порядке возрастания, используя функцию ascending() selectionSort(array, 8, ascending); printArray(array, 8); return 0; } |
Результат выполнения программы:
8 7 6 5 4 3 2 1
1 2 3 4 5 6 7 8
Прикольно, правда? Мы предоставили caller-у возможность контролировать процесс сортировки чисел (caller может определить любые другие функции сравнения):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
bool evensFirst(int a, int b) { // Если a - чётное число, а b - нечётное число, то a идет первым (никакого обмена местами не требуется) if ((a % 2 == 0) && !(b % 2 == 0)) return false; // Если a - нечётное число, а b - чётное число, то b идет первым (здесь уже требуется обмен местами) if (!(a % 2 == 0) && (b % 2 == 0)) return true; // В противном случае, сортируем в порядке возрастания return ascending(a, b); } int main() { int array[8] = { 4, 8, 6, 3, 1, 2, 5, 7 }; selectionSort(array, 8, evensFirst); printArray(array, 8); return 0; } |
Результат выполнения программы:
2 4 6 8 1 3 5 7
Как вы можете видеть, использование указателя на функцию позволяет caller-у «подключить» свой собственный функционал к чему-то, что мы писали и тестировали ранее, что способствует повторному использованию кода! Раньше, если вы хотели отсортировать один массив в порядке убывания, а другой — в порядке возрастания, вам понадобилось бы написать несколько версий сортировки массива. Теперь же у вас может быть одна версия, которая будет выполнять сортировку любым способом, каким вы только захотите!
Параметры по умолчанию в функциях
Если вы позволите caller-у передавать функцию в качестве параметра, то полезным будет предоставить и некоторые стандартные функции для удобства caller-а. Например, в вышеприведенном примере с сортировкой методом выбора, было бы проще установить дефолтный (по умолчанию) способ сравнения чисел. Например:
1 2 |
// Сортировка по умолчанию выполняется в порядке возрастания void selectionSort(int *array, int size, bool (*comparisonFcn)(int, int) = ascending); |
В этом случае, до тех пор, пока пользователь вызывает selectionSort() обычно (а не через указатель на функцию), параметр comparisonFcn
будет по умолчанию соответствовать функции ascending().
Указатели на функции и псевдонимы типов
Посмотрим правде в глаза — синтаксис указателей на функции уродлив. Тем не менее, с помощью typedefs мы можем исправить эту ситуацию:
1 |
typedef bool (*validateFcn)(int, int); |
Здесь мы определили псевдоним типа под названием validateFcn
, который является указателем на функцию, которая принимает два значения типа int и возвращает значение типа bool.
Теперь вместо написания следующего:
1 |
bool validate(int a, int b, bool (*fcnPtr)(int, int)); // фу, какой синтаксис |
Мы можем написать следующее:
1 |
bool validate(int a, int b, validateFcn pfcn) // вот это другое дело |
Так гораздо лучше, не правда ли? Однако синтаксис определения самого typedef может быть несколько трудным для запоминания. В C++11 вместо typedef вы можете использовать type alias для создания псевдонима типа указателя на функцию:
1 |
using validateFcn = bool(*)(int, int); // type alias |
Это уже читабельнее, чем с typedef, так как имя псевдонима и его определение расположены на противоположных сторонах от оператора =
.
Использование type alias идентично использованию typedef:
1 |
bool validate(int a, int b, validateFcn pfcn) // круто, не так ли? |
Использование std::function в C++11
В C++11 ввели альтернативный способ определения и хранения указателей на функции, который выполняется с использованием std::function. std::function является частью заголовочного файла functional Cтандартной библиотеки C++. Для определения указателя на функцию с помощью этого способа вам нужно объявить объект std::function следующим образом:
1 2 3 |
#include <functional> bool validate(int a, int b, std::function<bool(int, int)> fcn); // указываем указатель на функцию с помощью std::function, которая возвращает bool и принимает два int-а |
Как вы можете видеть, тип возврата и параметры находятся в угловых скобках, а параметры еще и внутри круглых скобок. Если параметров нет, то внутренние скобки можно оставить пустыми. Здесь уже более понятно, какой тип возвращаемого значения и какие ожидаемые параметры функции.
Обновим наш предыдущий пример из раздела «Присваивание функции указателю на функцию» текущего урока, но уже с использованием std::function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#include <iostream> #include <functional> int boo() { return 7; } int doo() { return 8; } int main() { std::function<int()> fcnPtr; // объявляем указатель на функцию, который возвращает int и не принимает никаких параметров fcnPtr = doo; // fcnPtr теперь указывает на функцию doo() std::cout << fcnPtr(); // вызываем функцию как обычно return 0; } |
Заключение
Указатели на функции полезны, прежде всего, когда вы хотите хранить функции в массиве (или в структуре) или когда вам нужно передать одну функцию в качестве аргумента другой функции. Поскольку синтаксис объявления указателей на функции является несколько уродливым и подвержен ошибкам, то рекомендуется использовать type alias (или std::function в C++11).
Тест
Задание №1
В этот раз мы попытаемся написать версию базового калькулятора с помощью указателей на функции.
a) Напишите короткую программу, которая просит пользователя ввести два целых числа и выбрать математическую операцию: +
, -
, *
или /
. Убедитесь, что пользователь ввел корректный символ математической операции (используйте проверку).
Ответ 1.a)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
#include <iostream> int getInteger() { std::cout << "Enter an integer: "; int a; std::cin >> a; return a; } char getOperation() { char op; do { std::cout << "Enter an operation ('+', '-', '*', '/'): "; std::cin >> op; } while (op!='+' && op!='-' && op!='*' && op!='/'); return op; } int main() { int a = getInteger(); char op = getOperation(); int b = getInteger(); return 0; } |
b) Напишите функции add(), subtract(), multiply() и divide(). Они должны принимать два целочисленных параметра и возвращать целочисленное значение.
Ответ 1.b)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
int add(int a, int b) { return a + b; } int subtract(int a, int b) { return a - b; } int multiply(int a, int b) { return a * b; } int divide(int a, int b) { return a / b; } |
c) Создайте typedef с именем arithmeticFcn
для указателя на функцию, которая принимает два целочисленных параметра и возвращает целочисленное значение.
Ответ 1.c)
1 |
typedef int (*arithmeticFcn)(int, int); |
d) Напишите функцию с именем getArithmeticFcn(), которая принимает символ выбранного математического оператора и возвращает соответствующую функцию в качестве указателя на функцию.
Ответ 1.d)
1 2 3 4 5 6 7 8 9 10 11 |
arithmeticFcn getArithmeticFcn(char op) { switch (op) { default: // функцией по умолчанию будет add() case '+': return add; case '-': return subtract; case '*': return multiply; case '/': return divide; } } |
e) Добавьте в функцию main() вызов функции getArithmeticFcn().
Ответ 1.e)
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#include <iostream> int main() { int a = getInteger(); char op = getOperation(); int b = getInteger(); arithmeticFcn fcn = getArithmeticFcn(op); std::cout << a << ' ' << op << ' ' << b << " = " << fcn(a, b) << '\n'; return 0; } |
f) Соедините все части вместе.
Полная программа
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
#include <iostream> int getInteger() { std::cout << "Enter an integer: "; int a; std::cin >> a; return a; } char getOperation() { char op; do { std::cout << "Enter an operation ('+', '-', '*', '/'): "; std::cin >> op; } while (op!='+' && op!='-' && op!='*' && op!='/'); return op; } int add(int a, int b) { return a + b; } int subtract(int a, int b) { return a - b; } int multiply(int a, int b) { return a * b; } int divide(int a, int b) { return a / b; } typedef int (*arithmeticFcn)(int, int); arithmeticFcn getArithmeticFcn(char op) { switch (op) { default: // функцией по умолчанию будет add() case '+': return add; case '-': return subtract; case '*': return multiply; case '/': return divide; } } int main() { int a = getInteger(); char op = getOperation(); int b = getInteger(); arithmeticFcn fcn = getArithmeticFcn(op); std::cout << a << ' ' << op << ' ' << b << " = " << fcn(a, b) << '\n'; return 0; } |
Задание №2
Теперь давайте изменим программу, которую мы написали в 1-м задании, чтобы переместить логику из getArithmeticFcn в массив.
a) Создайте структуру с именем arithmeticStruct
, которая имеет два члена: математический оператор типа char и указатель на функцию arithmeticFcn
.
Ответ 2.a)
1 2 3 4 5 |
struct arithmeticStruct { char op; arithmeticFcn fcn; }; |
b) Создайте статический глобальный массив arithmeticArray
, используя структуру arithmeticStruct
, который будет инициализирован каждой из 4 математических операций.
Ответ 2.b)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// Версия до C++11: static arithmeticStruct arithmeticArray[] = { { '+', add }, { '-', subtract }, { '*', multiply }, { '/', divide } }; // Версия C++11 с использованием uniform-инициализации static arithmeticStruct arithmeticArray[] { { '+', add }, { '-', subtract }, { '*', multiply }, { '/', divide } }; |
c) Измените getArithmeticFcn
для выполнения цикла по массиву и возврата соответствующего указателя на функцию.
Подсказка: Используйте цикл foreach.
Ответ 2.c)
1 2 3 4 5 6 7 8 9 10 |
arithmeticFcn getArithmeticFcn(char op) { for (auto &arith : arithmeticArray) { if (arith.op == op) return arith.fcn; } return add; // функцией по умолчанию будет add() } |
d) Соедините все части вместе.
Полная программа
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
#include <iostream> int getInteger() { std::cout << "Enter an integer: "; int a; std::cin >> a; return a; } char getOperation() { char op; do { std::cout << "Enter an operation ('+', '-', '*', '/'): "; std::cin >> op; } while (op != '+' && op != '-' && op != '*' && op != '/'); return op; } int add(int a, int b) { return a + b; } int subtract(int a, int b) { return a - b; } int multiply(int a, int b) { return a * b; } int divide(int a, int b) { return a / b; } typedef int(*arithmeticFcn)(int, int); struct arithmeticStruct { char op; arithmeticFcn fcn; }; static arithmeticStruct arithmeticArray[] { { '+', add }, { '-', subtract }, { '*', multiply }, { '/', divide } }; arithmeticFcn getArithmeticFcn(char op) { for (auto &arith : arithmeticArray) { if (arith.op == op) return arith.fcn; } return add; // функцией по умолчанию будет add() } int main() { int a = getInteger(); char op = getOperation(); int b = getInteger(); arithmeticFcn fcn = getArithmeticFcn(op); std::cout << a << ' ' << op << ' ' << b << " = " << fcn(a, b) << '\n'; return 0; } |
Всем здравствуйте!
Первый раз так погряз, помогите, пожалуйста. Не могу понять почему работает (пр.1):
Но реализуя через присваивание в глобальной области видимости следующим образом, ругается VS (пр.2):
А также, если вдруг знаете — подскажите: почему синтаксис, указанный в (пр.1) не работает с std::array? Заранее спасибо!
Добрый день, друзья! Появился вопрос после выполнения теста 2b, может кто-то знает: почему при инициализации массива arithmeticArray с помощью std::array требуется вложенность двух пар фигурных скобок?
С typedef следующая функция записывается так:
Как записать эту же функцию без typedef?
Так не пропускает:
Почему здесь && работает как | | ?
Я тоже было затупила, потом просто прочитала:
"До тех пор пока: op НЕ равно + И op НЕ равно — И ор НЕ равно * И ор НЕ равно /". И стало понятно. Иными словами оператор while пробегается по всем этим условиям, и если видит, что op не удовлетворяет ни одному из условий, т.е. И ни тому, И не этому, И не последнему, цикл повторяется. Иначе завершается.
Подскажите а возможно сделать хитрость и считывать название функции с текстового файла или со строки?
так как foo — адрес функции ничего не работает
Здравствуйте уважаемые пользователи и автор. Никак не могу понять что происходит в этих строках:
Получается мы объявили указатель на функцию типа int псевдонимом typedef, который принимает 2 целочисленных параметра. Как я понял:
arithmeticFcn getArithmeticFcn(char op) этот указатель указывает на функции add, subtract,multiply,devide, в зависимости от того какой case (оператор) передастся в функцию getArithmeticFcn(char op).
Но вот эта запись: arithmeticFcn fcn = getArithmeticFcn(op); не понятна, т.к. указателю можно вроде присвоить только тот тип функции, которым он объявлен. Соответственно не могу понять как правильно записать места использования указателя на функцию без typedef.
Также не ясна эта запись arithmeticFcn getArithmeticFcn(char op). Уважаемый автор или участники чата, если сможете пояснить, то прошу поясните.
Воспринимай это, как тип данных, который является указателем на функцию, которая принимает два значения типа int и возвращает значение типа int.(есть int, есть char и есть arithmeticFcn)
Здесь функция, которая принимает переменнную типо char и возвращает переменную типо arithmeticFcn, который является указателем на функцию, которая принимает два значения типа int и возвращает значение типа int.
Здесь мы создаем и инициализируем копированием переменную типа arithmeticFcn, функцией возвращаемым значением типа arithmeticFcn, который является указателем на функцию, которая принимает два значения типа int и возвращает значение типа int.
Юрий спасибо за пояснения, есть понимание. Доходит к сожалению не с первого раза.
Без typedef наверно понятнее будет.
Пропущено название псевдонима после "bool (*validateFcn)(int, int);"?
Здесь всё ок, ничего не пропущено. Мы же определили псевдоним типа, а идентификатор псевдонима типа (в данном случае
pfcn
) используется уже непосредственно в коде при применении псевдонима типа.Худо-бедно что-то написал. Естественно подсматривал и изучал почему так. Критикуйте, предлагайте.
Тип возвращаемого объекта в функциях chyslo и symbol лучше сделать void и ничего не возвращать, т.к. ты параметры в эти функции передаешь по ссылке.
Не понял. А зачем делать массив static? Для того чтобы область видимости ограничивалась только этим файлом?
Ну в данном случае static несет в себе именно этот посыл. Просто для того что бы другие юниты трансляции не имели доступа к этой переменной. С таким же успухом можно его объявить спецификатором const вместо static.
Написать функцию, которая считает количество слов в переданной строке. Слова отделены друг от друга пробелами и знаками препинания (.,!?-). Учтите, что эти знаки могут идти друг за другом. К примеру, «Привет … Мир!» – здесь 2 слова.
Не знаю нужен ли тебе ещё код по твоей задачке (скорее всего нет), но вот мой, вроде как рабочий, прототип:
Объясните пожалуйста про псевдоним в тесте.(1c) тут просто не недопонимания у меня
Где я провтыкал с указателем? Подскажите..
Все нормально с указателями. Переименуй функции minus() и plus(). Потому что такие названия для функций, являются неоднозначными в некоторых компиляторах.
Спасибо огромное за ваш непомерно большой труд! Ваша коллекция статей — это один из лучших русскоязычных ресурсов для изучения С++. Все очень удобно и доступно, UX у меня сложился очень хороший, еще раз, большое спасибо!
Очень приятно, что Равесли вам помог, за UX отдельное спасибо))
Как такое величественное в книге за час изучить? XD
Огромное спасибо автору за все уроки, они просто прекрасны!
Пожалуйста))
Чем дальше в лес, тем злее волки 🙁 В голове каша от всех этих указателей, ссылок, указателей на ссылки, указателей на указатели
Вчера тоже голова была кругом, лучше еще раз перечитать и покодировать ссылки, указатели на переменные и функции.
Достаточно все просто, брак опыта и только.
Попытаюсь объяснить, что тут происходит.
Смотри — мы инициализировали функции на принятие двух переменных и оператора.
Вот, далее мы инициализируем функции на математические вычисления. Называем соответственно.
Все просто. Тут мы походим к инициализации функции, ссылки на функции через typedef, она в свою очередь работает с двумя целочисловыми переменными, также как и те 4 функции, на мат. операции.
Далее инициализируем структуру для удобства, в которой есть наш оператор и ссылка на функцию(функции мат. вычислений).
После этого инициализация массива через эту структуру. Определенному оператору, определенная функция.
Тут самое интересное — передача данных другой функции.
Тут foreach, в котором идет объявление ссылки на переменную, для быстродействия, которая принимает каждое значение массива и проверяется ввод — найден, держи функцию.
В main() идет выполнения нашего ввода и передача данных с действий описанных выше и передача в fcn.
fcn(a, b) просто принимает ввод и идет вычисление.
Почему мы во втором задании, когда возвращаем при нахождении оп
arith.fcn;?
допустим arith сразу нашёл оп (op) под строкой 0 (т.е +) и вот он сравнил и оказалось что ввели и в правду + и он возвращает return arith.fcn;
и на что он указывает и что он означает ?
Он указывает как раз таки на функцию add()
Спасибо за содержательный урок! Особенно понравились typedef, <functional> и alias
Пожалуйста)
Юрий ты красава, уважаю от души
@@@
Все алгоритмы сортировки работают по одинаковой схеме: алгоритм сортировки выполняет итерацию по списку чисел, сравнивает пары чисел и меняет их местами, исходя из результатов этих сравнений.
@@@
Я бы советовал эту строку убрать или скорректировать. Это ведь неправда 🙂
По такой схеме работают сортировки, основанные на сравнении и обмене. Есть сортировки, основанные на сравнении, но в которых нет обменов. Есть сортировки, НЕ основанные на сравнении.
Например, сортировка слиянием — сравнения есть, обменов нет
некоторые реализации "быстрой сортировки" — нет сравнения между элементами (хотя есть сравнения в принципе), обменов может не быть
Сортировка подсчетом (а так же корзинная и радикс) — нет сравнений вообще
Сортировка вставками — нет сравнений между элементами (но есть сравнения вообще), нет обменов
А я не про копировать-вставить говорю, а про синтаксис. Причем тут думать, если синтаксис не давался? Как я могу пониманием придти вот к этому?:
Это должно было быть прокомментировано в уроке о массивах или структурах.
Это не камень в огород, а мои замечания. После просмотра ответов я это понял синтаксис, но его нельзя получить "пониманием". Синтаксис — это правила, а не выводы. А правила должны быть описаны.
Вполне возможно, что этот синтаксис и не разбирался в данном пособии. Хотя не факт, я не проверял просто. Но кто мешает Вам написать 8 строк и присвоить эти значения по отдельности каждому полю? Потом посмотреть в ответ и уяснить для себя, что возможен и такой синтаксис?
Нет в мире ничего идеального и данное пособие, скорее всего, тоже страдает огрехами. Хотя более полного БЕСПЛАТНОГО труда я не видел! И огромное спасибо Юрию за его бескорыстное вложение сил и средств!
Но, что самое смешное, Вы пытаетесь предъявить претензии переводчику, а не автору!
Нашел этот синтаксис в уроке 78. Многомерные массивы. Инициализация двумерных массивов.
Получается каждый член структуры — это столбец, а количество математических операций — строки.
Нужно подсказку давать.
Как в данном задании записать массив через std::array? Т.к. ругается, что "слишком много инициализаторов" (с 3-й строчки).
А зачем в строке (62) "for (auto &arith : arithmeticArray)" использовать "&" ведь и без него будет работать "for (auto arith : arithmeticArray)".
Забыл добавить, что при таком объёме данных ссылку нет смысла использовать. там нет таких затрат память, что бы ссылка хоть как то помогала.
На самом деле — соглашусь.
Суть в том, что входит все в привычку, образовуеться условный рефлекс и потом, когда будет этих данных много.
Скажем пользователь и пароль для системы, а это уже тысячи, забыл поставить, ибо в таких маленьких не ставил и пиши пропало.