При написании алгоритмов часто возникает ситуация, когда какую-то функцию нужно вызвать с одинаковым количеством аргументов, но с разными типами.
Давайте решим.
Надуманный пример на C++:
Тот же код в Javascript:#include <iostream> #include <string.h> using namespace std; void a(int x) { cout<<"number\n"; } void a(float x) { cout<<"number\n"; } void a(const char *x) { cout<<"string\n"; } template <typename T> void f(void(*g)(T x), T x) { g(x); } int main() { f(a, 1); f(a, 1.0f); f(a, "Alexey"); return 0; }
function f(g, x)
{
g(x)
}
function a(x)
{
alert(typeof x)
}
f(a, 1)
f(a, 1.0)
f(a, 'Alexey')
Очевидно, что код JavaScript лучше.
Давайте разберемся, почему это происходит.
Динамический подход
В javascript каждая переменная имеет свойство type, которое возвращается, когда к переменной применяется оператор typeof. Это свойство сохраняется во время выполнения программы, когда переменная существует. И когда к переменной применяется какой-то оператор, функция, реализующая этот оператор, проверяет тип переменной.В зависимости от типа выполняются некоторые действия.
Например, 2+2 вернет 4, 2+"Алексей" вернет "2Алексей".
Те.
Операторы языка почти всегда обязаны проверять типы переменных, к которым они применяются.
Назовем этот подход «динамической типизацией».
Недостатки такого подхода вполне очевидны, а точнее один из них – необходимость производить дополнительные вычисления во время выполнения программы.
Преимуществом будет код, в котором нет явного понятия типа, а только последовательность действий, что положительно влияет на читабельность.
Еще одним преимуществом станет код, в котором нет необходимости дублировать значительную часть логики.
Это слегка видно в примере выше — функция f реализуется один раз, но результат ее выполнения зависит от переданной в нее функции, реализующей различную логику.
Статический подход
Язык C требует указания типа переменной и запрещает его изменение.В результате во время выполнения программы проверки типов не выполняются; операторы применяются к переменным однозначно.
Но красивых механизмов обобщения не существует. Что это такое: универсальный указатель void*, возможность передать в функцию указатель на другую функцию.
В языке C++, который в некотором роде является расширением языка C, появились такие механизмы, как полиморфизм функций (перегрузка) и шаблоны.
Полиморфизм функций позволяет вам назвать две функции, которые делают одно и то же, но с разными типами данных и одинаковыми именами.
Шаблоны представляют новый тип, общий для всех типов.
Эти механизмы хороши тем, что с их помощью без участия программиста подставляются функции нужных типов.
Те.
если один шаблон функции использует 2 разных типа, то во время компиляции будут созданы 2 разные функции.
При вызове исходной функции в коде в скомпилированный файл будет подставлен вызов скопированной функции нужного типа.
Преимущества подхода: максимальная скорость выполнения.
Минусы: для выполнения требуется больше памяти.
Вывод типа
Другими словами, когда необходима максимальная производительность и достаточно памяти, статический подход более предпочтителен.Но при выборе, скажем, языка C++ теряется читаемость кода.
Ведь для каждого чиха приходится указывать тип, с которым вызывается функция.
Например, код быстрой сортировки: #include <iostream>
#include <string.h>
using namespace std;
template<class T>
void qsort(T** array, int length, int compare(T *a, T *b))
{
int left = 0;
int right = length-1;
T *middle_element;
middle_element=array[length/2];
do
{
while( compare(array[left], middle_element)<0 )
left++;
while( compare(array[right], middle_element)>0 )
right--;
if(left<=right)
{
swap(array[left], array[right]);
left++;
right--;
}
}
while(left<=right);
if(right>0)
qsort(array, right, compare);
if(left<length)
qsort(array+left, length-left, compare);
}
int cmp(char *a, char *b)
{
return strcmp(a, b);
}
int main()
{
char *strings[]={"Alexey", "Borisenko"};
qsort(strings, 2, cmp);
return 0;
}
Полностью удалив типы, вы только улучшите читабельность: #include <iostream>
#include <string.h>
using namespace std;
qsort(array, length, compare)
{
left = 0;
right = length-1;
middle_element = array[length/2];
do
{
while( compare(array[left], middle_element)<0 )
left++;
while( compare(array[right], middle_element)>0 )
right--;
if(left<=right)
{
swap(array[left], array[right]);
left++;
right--;
}
}
while(left<=right);
if(right>0)
qsort(array, right, compare);
if(left<length)
qsort(array+left, length-left, compare);
}
cmp(a, b)
{
return strcmp(a, b);
}
main()
{
strings={"Alexey", "Borisenko"};
qsort(strings, 2, cmp);
return 0;
}
Из этих соображений был сделан вывод, что отказ от типов — хорошая идея.
Давайте подумаем, как это реализовать.
Существует 3 основных типа: 1) Простой 2) Композитный 3) Функция Простой тип включает в себя все типы, которые не содержат других (строка, число, массив.
).
Соответственно, для составления составных структур, таких как гетерогенный массив, структура и т. д. Функция выделяется тем, что у нее есть аргументы, которые сами являются типами.
Итак, вывод типа осуществляется в ходе следующих операций: 1) Назначение 2) Вызов функции Во время вывода типа переменная не может изменить тип.
Доказательство от противного: f(a)
{
x=1
if(a<2)
Теги: #выведение типа #JavaScript #программирование #C++ #Алгоритмы #C++
-
Универсальный Вирус
19 Oct, 24 -
В России Создают «Резервную Копию» Рунета
19 Oct, 24 -
Исходные Коды Лента. Ru
19 Oct, 24