Связанные элементы имеют несколько интересных ответов.
Тони
Эти ответы не охватывают проблему, которая intможет быть недостаточно большой! ( [C++03: 7.2/5])
Гонки легкости на орбите
Интересно, что вы можете определить operator++по перечислениям; тем не менее, так что вы можете сделать for(Enum_E e = (Enum_E)0; e < ENUM_COUNT; e++). Обратите внимание, что вы должны привести 0к, Enum_Eпотому что C ++ запрещает операторы присваивания для перечислений
weberc2
Если бы существовал оператор времени компиляции, аналогичный тому, как работает sizeof, который мог бы генерировать литерал std :: initializer_list, состоящий из значений перечисления, у нас было бы решение и не было бы никаких накладных расходов времени выполнения.
Обратите внимание, перечисление Last предназначено для пропуска итерации. Используя это «поддельное» Lastперечисление, вам не нужно обновлять условие завершения в цикле for до последнего «реального» перечисления каждый раз, когда вы хотите добавить новое перечисление. Если вы хотите добавить другие перечисления позже, просто добавьте их до Last. Цикл в этом примере все еще будет работать.
Конечно, это ломается, если указаны значения перечисления:
enumFoo{One=1,Two=9,Three=4,Last};
Это показывает, что перечисление на самом деле не предназначено для итерации. Типичный способ работы с enum - это использовать его в операторе switch.
Если вы действительно хотите перечислить, поместите значения перечисления в вектор и выполните итерации по этому. Это будет правильно работать с указанными значениями перечисления.
Обратите внимание, что в первой части примера, если вы хотите использовать 'i' в качестве перечисления Foo, а не int, вам нужно будет выполнить статическое приведение типа: static_cast <Foo> (i)
Clayton
5
Также вы пропускаете Последнее в цикле. Должно быть <= Последнее
Тони
20
@Tony Last предназначен для пропуска. Если вы хотите добавить больше перечислений позже, добавьте их до Last ... цикл в первом примере все еще будет работать. Используя "поддельное" последнее перечисление, вам не нужно обновлять условие завершения в цикле for до последнего "реального" перечисления каждый раз, когда вы хотите добавить новое перечисление.
Timidpueo
За исключением того, что вы фактически распределили память, когда перечисление при условии, что оно имеет нулевое индексирование и является строго непрерывным, может выполнить эту задачу без выделения памяти.
Облако
1
Обратите внимание, что для того, чтобы это определение перечисления было безопасным для обновлений, необходимо определить значение UNKNOWN = 0. Кроме того, я бы предложил исключить defaultрегистр при переключении значений перечисления, поскольку это может скрыть случаи, когда обработка значений была забыта до времени выполнения. Вместо этого следует жестко закодировать все значения и использовать UNKNOWNполе для обнаружения несовместимостей.
Бенджамин Банье
53
#include<iostream>#include<algorithm>namespaceMyEnum{enumType{
a =100,
b =220,
c =-1};staticconstTypeAll[]={ a, b, c };}void fun(constMyEnum::Type e ){
std::cout << e << std::endl;}int main(){// allfor(constauto e :MyEnum::All)
fun( e );// somefor(constauto e :{MyEnum::a,MyEnum::b })
fun( e );// all
std::for_each( std::begin(MyEnum::All), std::end(MyEnum::All), fun );return0;}
Спасибо! Обратите внимание, что если вы пересекаете файлы / классы и если совместимость с MS создает проблемы с объявленными в заголовке нецелыми константами, в моем компиляторе помогает явно указать размер в типе в заголовке: static const Type All[3];и тогда я могу для инициализации в источнике: const MyEnum::Type MyEnum::All[3] = { a, b, c }; перед этим я получал неприятные Error in range-based for...ошибки (потому что массив имел неизвестный размер). Понял это благодаря связанному ответу
мудрец
1
Версия массива очень дружелюбна к копированию и вставке. Наиболее удовлетворительный ответ, кроме того, «НЕТ» или «только для последовательного». Вероятно, макрос дружественный даже.
Пауло Невес
1
это может быть хорошим решением для перечислений с небольшим количеством элементов, но для перечислений с большим количеством элементов оно не должно подходить.
kato2
20
Если ваше перечисление начинается с 0, а приращение всегда равно 1.
С c ++ 11, на самом деле, есть альтернатива: написание простого шаблонного пользовательского итератора.
давайте предположим, что ваше перечисление
enumclass foo {
one,
two,
three
};
Этот универсальный код довольно эффективно справится с задачей - поместит его в универсальный заголовок, он послужит вам для любого перечисления, которое вам может понадобиться для перебора:
#include<type_traits>template<typename C, C beginVal, C endVal>classIterator{typedeftypename std::underlying_type<C>::type val_t;int val;public:Iterator(const C & f): val(static_cast<val_t>(f)){}Iterator(): val(static_cast<val_t>(beginVal)){}Iteratoroperator++(){++val;return*this;}
C operator*(){returnstatic_cast<C>(val);}Iterator begin(){return*this;}//default ctor is goodIterator end(){staticconstIterator endIter=++Iterator(endVal);// cache itreturn endIter;}booloperator!=(constIterator& i){return val != i.val;}};
И тогда вы можете итерировать с помощью диапазона для
for(foo i : fooIterator()){//notice the parentheses!
do_stuff(i);}
Предположение, что у вас нет пробелов в вашем перечислении, все еще верно; нет никакого предположения о количестве битов, фактически необходимых для хранения значения перечисления (благодаря std :: basic_type)
Я не знаю, почему это было понижено. Это разумное решение.
Пол Браннан
11
Я ожидаю, что это потому, что записи должны поддерживаться в двух местах.
Муравей
Разрешает ли C ++ for (NodePosition pos : NodePositionVector)синтаксис? Насколько я знаю, это синтаксис Java, и вам понадобятся итераторы в C ++, чтобы сделать что-то эквивалентное.
thegreatjedi
3
@thegreatjedi Начиная с C ++ 11 вы можете, даже проще: for (auto pos: NodePositionVector) {..}
Enzojz
@thegreatjedi Было бы быстрее найти или даже скомпилировать тестовую программу, чем задать этот вопрос. Но да, начиная с C ++ 11, это совершенно правильный синтаксис C ++, который компилятор переводит в эквивалентный (и гораздо более подробный / менее абстрактный) код, обычно через итераторы; см. cppreference . И, как сказал Enzojz, C ++ 11 также добавил auto, так что вам не нужно явно объявлять тип элементов, если вам (A) не нужно использовать оператор преобразования или (B) autoпо какой-то причине не нравится , Большинство forпользователей диапазона используют autoAFAICT
underscore_d
9
Я часто так делаю
enumEMyEnum{
E_First,
E_Orange = E_First,
E_Green,
E_White,
E_Blue,
E_Last
}for(EMyEnum i = E_First; i < E_Last; i =EMyEnum(i +1)){}
или, если не последовательно, но с регулярным шагом (например, битовые флаги)
но какая польза от распечатки значений побитовым образом?
Ану
1
Использовать перечисления для создания битовых масок. например, объединить несколько параметров в одну переменную, а затем использовать FOR для проверки каждого параметра. Исправлен мой пост с лучшим примером.
Ники,
Я до сих пор не могу его использовать (и ваш пост все еще показывает старый пример)! Использование enum в качестве битовых масок действительно полезно, но не удалось соединить точки! Не могли бы вы подробнее рассказать о вашем примере, вы также можете добавить дополнительный код.
Ану
@anu Извините, не увидел ваш комментарий. В качестве примера битовой маски добавлен класс Frog
Ники,
8
Вы не можете с перечислением. Может быть, enum не совсем подходит для вашей ситуации.
Общепринятым соглашением является присвоение имени последнему перечисляемому значению что-то вроде MAX и использование его для управления циклом с использованием int.
Здесь есть несколько примеров, демонстрирующих обратное. По твоему утверждению ты сам себе противоречишь (вторая строка).
Ники,
6
Что-то, что не было рассмотрено в других ответах = если вы используете строго типизированные перечисления C ++ 11, вы не можете использовать ++или + intдля них. В этом случае требуется более сложное решение:
enumCount{ zero, one, two, three };
for_range (Count, c, zero, three){
cout <<"forward: "<< c << endl;}
Его можно использовать для итерации вперед и назад через unsigned, integer, enums и chars:
for_range (unsigned, i,10,0){
cout <<"backwards i: "<< i << endl;}
for_range (char, c,'z','a'){
cout << c << endl;}
Несмотря на неловкое определение, он очень хорошо оптимизирован. Я посмотрел на дизассемблер в VC ++. Код чрезвычайно эффективен. Не стоит откладывать, кроме трех утверждений for: компилятор выдаст только один цикл после оптимизации! Вы даже можете определить вложенные циклы:
unsigned p[4][5];
for_range (Count, i, zero,three)
for_range(unsignedint, j,4,0){
p[i][j]=static_cast<unsigned>(i)+j;}
Очевидно, вы не можете перебирать перечисляемые типы с пробелами.
Вы не можете перегружать любые операторы в перечисляемых типах C или C ++. Если только вы не должны были создать структуру / класс, который эмулировал бы перечисление значений.
Чрезмерное увеличение / уменьшение требует принятие решения о том , что делать , когда происходит переполнение
Одноименное
3
Предполагая, что перечисление нумеруется последовательно, подвержено ошибкам. Кроме того, вы можете перебирать только выбранные перечислители. Если это подмножество мало, зацикливание на нем явно может быть элегантным выбором:
enumItem{Man,Wolf,Goat,Cabbage};// or enum classfor(auto item :{Wolf,Goat,Cabbage}){// or Item::Wolf, ...// ...}
Это хороший вариант, я думаю. Должно ли быть частью более новой спецификации C ++, чем я использовал, когда задавал вопрос, который угадаю?
Адам
Да. Он перебирает std :: initializer_list <Item>. ссылка .
Марски
2
Если вам не нравится загрязнять ваше перечисление конечным элементом COUNT (потому что, возможно, если вы также используете перечисление в переключателе, тогда компилятор предупредит вас о пропущенном регистре COUNT :), вы можете сделать это:
enumColour{Red,Green,Blue};constColourLastColour=Blue;Colour co(0);while(true){// do stuff with co// ...if(co ==LastColour)break;
co =Colour(co+1);}
Вот еще одно решение, которое работает только для смежных перечислений. Он дает ожидаемую итерацию, за исключением уродства в приращении, где оно и есть, поскольку именно это сломано в C ++.
enumBar{One=1,Two,Three,End_Bar// Marker for end of enum; };for(Bar foo =One; foo <End_Bar; foo =Bar(foo +1)){// ...}
Увеличение может быть сокращено до foo = Bar(foo + 1).
HolyBlackCat
Спасибо, HolyBlackCat, я включил ваше отличное предложение! Я также заметил, что Riot имеет почти то же самое решение, но соответствующее строгой типизации (и, следовательно, более многословное).
Итан Брэдфорд
2
enumclass A {
a0=0, a3=3, a4=4};constexpr std::array<A,3> ALL_A {A::a0, A::a3, A::a4};// constexpr is important herefor(A a: ALL_A){if(a==A::a0 || a==A::a4) std::cout <<static_cast<int>(a);}
A constexpr std::arrayможет повторять даже непоследовательные перечисления без создания экземпляра массива компилятором. Это зависит от таких вещей, как эвристика оптимизации компилятора и от того, берете ли вы адрес массива.
В моих экспериментах я обнаружил, что g++9.1 с -O3оптимизирует вышеприведенный массив, если есть 2 непоследовательных значения или довольно много последовательных значений (я проверял до 6). Но он делает это только если у вас есть ifзаявление. (Я пробовал оператор, который сравнивал целочисленное значение больше, чем все элементы в последовательном массиве, и он включал итерацию, хотя ни один из них не был исключен, но когда я пропустил оператор if, значения были помещены в память.) значения из непоследовательного перечисления в [одном случае | https://godbolt.org/z/XuGtoc] . Я подозреваю, что это странное поведение связано с глубокой эвристикой, связанной с кэшем и предсказанием ветвлений.
Мне нравится простая семантика циклического цикла, и я думаю, что она будет развиваться еще больше, поэтому мне нравится это решение.
rtischer8277
2
В книге Бьярна Страуструпа по языку программирования C ++ вы можете прочитать, что он предлагает перегрузить ее operator++для ваших конкретных задач enum.enumявляются определяемыми пользователем типами и оператор перегрузки существует на языке для этих конкретных ситуаций.
Вы сможете кодировать следующее:
#include<iostream>enumclassColors{red, green, blue};Colors&operator++(Colors&c,int){switch(c){caseColors::red:return c=Colors::green;caseColors::green:return c=Colors::blue;caseColors::blue:return c=Colors::red;// managing overflowdefault:throw std::exception();// or do anything else to manage the error...}}int main(){Colors c =Colors::red;// casting in int just for convenience of output.
std::cout <<(int)c++<< std::endl;
std::cout <<(int)c++<< std::endl;
std::cout <<(int)c++<< std::endl;
std::cout <<(int)c++<< std::endl;
std::cout <<(int)c++<< std::endl;return0;}
Имейте в виду, что я использую enum class. Код прекрасно работает enumтакже. Но я предпочитаю, enum classтак как они строго типизированы и могут помешать нам ошибиться во время компиляции.
На этот пост был подан отрицательный ответ. Любая причина, почему бы не ответить на вопрос?
LAL
Причина, вероятно, в том, что это ужасное архитектурно говоря решение: оно заставляет вас писать глобальную логику, предназначенную для конкретного компонента (вашего перечисления), более того, если ваше перечисление действительно меняется по какой-то причине, вы вынуждены редактировать свой + + оператор, так как этот подход не является устойчивым для любого проекта среднего масштаба, неудивительно, что он исходит из рекомендации Бьярно Страуструпа, в те времена, когда архитектура программного обеспечения была похожа на научную фантастику
Манджа
Оригинальный вопрос о том, чтобы иметь оператора enum. Это был не архитектурный вопрос. Я не верю, что в 2013 году C ++ был научной фантастикой.
LAL
1
Для MS-компиляторов:
#define inc_enum(i)((decltype(i))((int)i +1))enum enumtype { one, two, three, count};for(enumtype i = one; i < count; i = inc_enum(i)){
dostuff(i);}
Примечание: это намного меньше кода, чем простой шаблонный пользовательский ответ итератора.
Вы можете заставить это работать с GCC, используя typeofвместо этого decltype, но у меня нет этого компилятора под рукой, чтобы убедиться, что он компилируется.
Это было написано примерно через 5 лет после того, как decltypeстало стандартом C ++, поэтому не стоит рекомендовать устаревшие typeofиз древних GCC. Смутно недавний GCC справляется decltypeпросто отлично. Есть и другие проблемы: отбрасывание в стиле C не рекомендуется, а макросы хуже. Правильные функции C ++ могут дать ту же общую функциональность. Это было бы лучше переписать использовать static_castи шаблонную функцию: template <typename T> auto inc_enum(T const t) { return static_cast<T>(static cast<int>(t) + 1); }. И броски не нужны для не enum class. Кроме того, операторы могут быть перегружены по enumтипу (TIL)
underscore_d
1
typedefenum{
first =2,
second =6,
third =17}MyEnum;staticconstint enumItems[]={
first,
second,
third
}staticconstintEnumLength=sizeof(enumItems)/sizeof(int);for(int i =0; i <EnumLength; i++){//Do something with enumItems[i]}
Это решение создаст ненужные статические переменные в памяти, в то время как цель enum состоит в том, чтобы просто создать «маску» для встроенных констант
TheArquitect
Если не изменено наconstexpr static const int enumItems[]
Мохаммад Канан
0
В C ++ нет самоанализа, поэтому вы не можете определить такие вещи во время выполнения.
Не могли бы вы объяснить мне, почему «самоанализ» был бы необходим для итерации по перечислению?
Джонатан Ми
Может быть, термин « отражение» ?
kͩeͣmͮpͥ ͩ
2
Я пытаюсь сказать 2 вещи: 1) Во многих других ответах C ++ может выполнить это, поэтому, если вы собираетесь сказать, что это невозможно, требуется ссылка или дальнейшие разъяснения. 2) В нынешнем виде это в лучшем случае комментарий, а не ответ.
Джонатан Ми
Тогда понизьте
1
Я снова добавлю 2 комментария: 1) Я не понижаю голос, потому что я считаю, что получение понижающего голоса демотивирует участие на сайте, я считаю это контрпродуктивным 2) Я все еще не понимаю, что вы пытаетесь сказать, но это звучит как Вы понимаете что-то, чего я не знаю, и в этом случае я бы предпочел, чтобы вы уточнили, а не удалили отрицательный ответ.
Джонатан Ми
0
Если вы знали, что значения перечисления были последовательными, например, перечисление Qt: Key, вы могли бы:
int
может быть недостаточно большой! ([C++03: 7.2/5]
)operator++
по перечислениям; тем не менее, так что вы можете сделатьfor(Enum_E e = (Enum_E)0; e < ENUM_COUNT; e++)
. Обратите внимание, что вы должны привести0
к,Enum_E
потому что C ++ запрещает операторы присваивания для перечисленийОтветы:
Типичный способ заключается в следующем:
Обратите внимание, перечисление
Last
предназначено для пропуска итерации. Используя это «поддельное»Last
перечисление, вам не нужно обновлять условие завершения в цикле for до последнего «реального» перечисления каждый раз, когда вы хотите добавить новое перечисление. Если вы хотите добавить другие перечисления позже, просто добавьте их до Last. Цикл в этом примере все еще будет работать.Конечно, это ломается, если указаны значения перечисления:
Это показывает, что перечисление на самом деле не предназначено для итерации. Типичный способ работы с enum - это использовать его в операторе switch.
Если вы действительно хотите перечислить, поместите значения перечисления в вектор и выполните итерации по этому. Это будет правильно работать с указанными значениями перечисления.
источник
UNKNOWN = 0
. Кроме того, я бы предложил исключитьdefault
регистр при переключении значений перечисления, поскольку это может скрыть случаи, когда обработка значений была забыта до времени выполнения. Вместо этого следует жестко закодировать все значения и использоватьUNKNOWN
поле для обнаружения несовместимостей.источник
static const Type All[3];
и тогда я могу для инициализации в источнике:const MyEnum::Type MyEnum::All[3] = { a, b, c };
перед этим я получал неприятныеError in range-based for...
ошибки (потому что массив имел неизвестный размер). Понял это благодаря связанному ответуЕсли ваше перечисление начинается с 0, а приращение всегда равно 1.
Если нет, я думаю, единственное, почему нужно создать что-то вроде
добавить элементы и использовать обычные итераторы ....
источник
С c ++ 11, на самом деле, есть альтернатива: написание простого шаблонного пользовательского итератора.
давайте предположим, что ваше перечисление
Этот универсальный код довольно эффективно справится с задачей - поместит его в универсальный заголовок, он послужит вам для любого перечисления, которое вам может понадобиться для перебора:
Вам нужно будет специализировать это
И тогда вы можете итерировать с помощью диапазона для
Предположение, что у вас нет пробелов в вашем перечислении, все еще верно; нет никакого предположения о количестве битов, фактически необходимых для хранения значения перечисления (благодаря std :: basic_type)
источник
std::vector
это не универсально, потому чтоstd::vector<foo>
связано сfoo
.typedef Iterator<color, color::green, color::red> colorIterator;
Убедитесь, что вы понимаете, как работают экземпляры шаблона.foo operator*() { ...
должно бытьC operator*() { ...
.Слишком сложное решение, я делаю так:
источник
for (NodePosition pos : NodePositionVector)
синтаксис? Насколько я знаю, это синтаксис Java, и вам понадобятся итераторы в C ++, чтобы сделать что-то эквивалентное.auto
, так что вам не нужно явно объявлять тип элементов, если вам (A) не нужно использовать оператор преобразования или (B)auto
по какой-то причине не нравится , Большинствоfor
пользователей диапазона используютauto
AFAICTЯ часто так делаю
или, если не последовательно, но с регулярным шагом (например, битовые флаги)
источник
Вы не можете с перечислением. Может быть, enum не совсем подходит для вашей ситуации.
Общепринятым соглашением является присвоение имени последнему перечисляемому значению что-то вроде MAX и использование его для управления циклом с использованием int.
источник
Что-то, что не было рассмотрено в других ответах = если вы используете строго типизированные перечисления C ++ 11, вы не можете использовать
++
или+ int
для них. В этом случае требуется более сложное решение:источник
Вы можете попытаться определить следующий макрос:
Теперь вы можете использовать его:
Его можно использовать для итерации вперед и назад через unsigned, integer, enums и chars:
Несмотря на неловкое определение, он очень хорошо оптимизирован. Я посмотрел на дизассемблер в VC ++. Код чрезвычайно эффективен. Не стоит откладывать, кроме трех утверждений for: компилятор выдаст только один цикл после оптимизации! Вы даже можете определить вложенные циклы:
Очевидно, вы не можете перебирать перечисляемые типы с пробелами.
источник
_A1
это не разрешенное имя, это подчеркивание со следующей заглавной буквой.Вы также можете перегрузить операторы увеличения / уменьшения для вашего перечисляемого типа.
источник
Предполагая, что перечисление нумеруется последовательно, подвержено ошибкам. Кроме того, вы можете перебирать только выбранные перечислители. Если это подмножество мало, зацикливание на нем явно может быть элегантным выбором:
источник
Если вам не нравится загрязнять ваше перечисление конечным элементом COUNT (потому что, возможно, если вы также используете перечисление в переключателе, тогда компилятор предупредит вас о пропущенном регистре COUNT :), вы можете сделать это:
источник
Вот еще одно решение, которое работает только для смежных перечислений. Он дает ожидаемую итерацию, за исключением уродства в приращении, где оно и есть, поскольку именно это сломано в C ++.
источник
foo = Bar(foo + 1)
.A
constexpr std::array
может повторять даже непоследовательные перечисления без создания экземпляра массива компилятором. Это зависит от таких вещей, как эвристика оптимизации компилятора и от того, берете ли вы адрес массива.В моих экспериментах я обнаружил, что
g++
9.1 с-O3
оптимизирует вышеприведенный массив, если есть 2 непоследовательных значения или довольно много последовательных значений (я проверял до 6). Но он делает это только если у вас естьif
заявление. (Я пробовал оператор, который сравнивал целочисленное значение больше, чем все элементы в последовательном массиве, и он включал итерацию, хотя ни один из них не был исключен, но когда я пропустил оператор if, значения были помещены в память.) значения из непоследовательного перечисления в [одном случае | https://godbolt.org/z/XuGtoc] . Я подозреваю, что это странное поведение связано с глубокой эвристикой, связанной с кэшем и предсказанием ветвлений.Вот ссылка на простую итерацию теста на Godbolt, которая демонстрирует, что массив не всегда создается.
Ценой этой техники является написание элементов enum дважды и синхронизация двух списков.
источник
В книге Бьярна Страуструпа по языку программирования C ++ вы можете прочитать, что он предлагает перегрузить ее
operator++
для ваших конкретных задачenum
.enum
являются определяемыми пользователем типами и оператор перегрузки существует на языке для этих конкретных ситуаций.Вы сможете кодировать следующее:
тестовый код: http://cpp.sh/357gb
Имейте в виду, что я использую
enum class
. Код прекрасно работаетenum
также. Но я предпочитаю,enum class
так как они строго типизированы и могут помешать нам ошибиться во время компиляции.источник
enum
. Это был не архитектурный вопрос. Я не верю, что в 2013 году C ++ был научной фантастикой.Для MS-компиляторов:
Примечание: это намного меньше кода, чем простой шаблонный пользовательский ответ итератора.
Вы можете заставить это работать с GCC, используя
typeof
вместо этогоdecltype
, но у меня нет этого компилятора под рукой, чтобы убедиться, что он компилируется.источник
decltype
стало стандартом C ++, поэтому не стоит рекомендовать устаревшиеtypeof
из древних GCC. Смутно недавний GCC справляетсяdecltype
просто отлично. Есть и другие проблемы: отбрасывание в стиле C не рекомендуется, а макросы хуже. Правильные функции C ++ могут дать ту же общую функциональность. Это было бы лучше переписать использоватьstatic_cast
и шаблонную функцию:template <typename T> auto inc_enum(T const t) { return static_cast<T>(static cast<int>(t) + 1); }
. И броски не нужны для неenum class
. Кроме того, операторы могут быть перегружены поenum
типу (TIL)источник
constexpr static const int enumItems[]
В C ++ нет самоанализа, поэтому вы не можете определить такие вещи во время выполнения.
источник
Если вы знали, что значения перечисления были последовательными, например, перечисление Qt: Key, вы могли бы:
Работает как положено.
источник
Просто сделайте массив целых и сделайте цикл по массиву, но заставьте последний элемент сказать -1 и используйте его для условия выхода.
Если enum это:
затем создайте массив:
Это не ломается независимо от целых чисел в представлении, пока проверка -1 не сталкивается ни с одним из элементов, конечно.
источник