MetaProg

Описание: Разработка и отладка приложений. Упор на 3D-графику.

dyvniy M
Автор темы, Администратор
Администратор
Аватара
dyvniy M
Автор темы, Администратор
Администратор
Возраст: 41
Репутация: 1
Лояльность: 1
Сообщения: 3579
Зарегистрирован: Ср, 10 октября 2012
С нами: 11 лет 5 месяцев
Профессия: Программист
Откуда: Россия, Москва
ICQ Сайт Skype ВКонтакте

#61 dyvniy » Ср, 31 января 2018, 10:54:19

Лекции Технополиса. Проектирование высоконагруженных систем (осень 2017)
https://habrahabr.ru/company/odnoklassniki/blog/347798/
Изображение

dyvniy M
Автор темы, Администратор
Администратор
Аватара
dyvniy M
Автор темы, Администратор
Администратор
Возраст: 41
Репутация: 1
Лояльность: 1
Сообщения: 3579
Зарегистрирован: Ср, 10 октября 2012
С нами: 11 лет 5 месяцев
Профессия: Программист
Откуда: Россия, Москва
ICQ Сайт Skype ВКонтакте

#62 dyvniy » Вт, 20 марта 2018, 18:34:00

Что не так с ООП и ФП
https://habrahabr.ru/post/201874/
Спойлер
Что не так с ООП и ФП
http://yinwang0.wordpress.com/2013/11/09/oop-fp/
Функциональное программирование,
ООП,
Проектирование и рефакторинг
Перевод
Я не понимаю причины существования бесконечных споров вокруг Объектно-ориентированного (ООП) и Функционального (ФП) программирования. Кажется, что такого рода вещи находятся за пределами человеческого понимая, и о них можно спорить бесконечно. Много лет занимаясь исследованием языков программирования, я увидел четкий ответ, и зачастую я нахожу бессмысленным обсуждение этих вопросов.

Если кратко, то как ООП, так и ФП неэффективны, если доходить в их использовании до крайности. Крайностью в ООП считается идея о том что “все что угодно является объектом” (чистое ОП). Крайностью для ФП можно рассматривать чистые функциональные языки программирования.

Что не так с ООП

Наиболее ошибочным для ООП является само понятие “объекта” и попытка определить что-угодно через него. В конце концов, вы доходите до концепции “все что угодно — это объект”. Но это не так, потому что:

Существуют вещи, которые не являются объектами. Функции не являются объектами.

Вы можете возразить, что в Python или Scala функции являются объектами. В Python любой объект с методом __call__ можно считать функцией. Точно также в Scala, функции — это просто объекты с методом apply. Тем не менее, тщательно поразмышляв над этим, можно заметить несоответствие теории с практикой. Дело в том, что функции являются фундаментальным понятием, в то время как объекты лишь содержат их. Мы пытаемся определить функции как объекты, которые содержат методы __call__ или apply, но сами по себе эти методы уже являются “функциональными объектами”. Python и Scala просто похитили все функции, заключили их в тюрьму “объектов”, пометили как __call__ или apply, и в конце концов назвали это “методами”. Конечно, если обернуть все функции в объекты, появляется возможность обращаться с различными объектами как с функциями, но это еще не значит, что можно утверждать что “функции также являются объектами”.

Большинство ОО-языков также лишены нормальной реализации функций первого порядка. Кульминацией можно считать Java, которая вообще не позволяет передавать функции как данные. Да, вы всегда можете обернуть функции в объекты и назвать их “методами”, но, как я уже говорил, это всего лишь похищение. Отсутствие функции первого порядка является основной причиной того, что существует такое множество "​​шаблонов проектирования" в Java. Но как только у вас есть функции первого порядка, в большинстве таких шаблонов просто нет никакой необходимости.

Что не так с ФП

Подобно ООП, концепция функционального программирования начинает больше вредить, когда вы доходите до крайности, а именно до чистых функциональных языков программирования. Для лучшего понимания было бы не лишним понимать, что называют чистым функциональным языком. Как вариант, вы можете прочесть «What is a Purely Functional Language» авторства Amr Sabry (он был моим консультантом при получении PhD). Если говорить в общем, чистые функциональные языки являются неправильным, потому что:

Существуют вещи, которые не являются чистыми по своей сути. Побочные эффекты очень реальны.

Будучи чистыми, ФП-языки игнорируют возможности и ограничения, которые предоставляет физическая платформа (кремниевые чипы, квантовые чипы, не важно). Вместо этого ФП-языки пытаются заново реализовать окружающий мир, передавая на вход некоторых функций “состояние мира” и получая новое его состояние на выходе. Но существует разница между физическими процессами и их симуляцией. Побочные эффекты — это реальные процессы. Они действительно существуют в природе и являются необходимыми для того, чтобы эффективно производить вычисления. Попытка же имитировать побочные эффекты на основе чистых функций обречена быть не эффективной, сложной и скорее всего уродливой. Замечали ли вы, как легко реализовать на С кольцевую структуру данных или генератор случайных чисел? То же самое нельзя сказать о Haskell.

Кроме того, программирование на чистых функциональных языках требует огромных умственных усилий. Если вы посмотрите глубже, то поймете что монады делают программы переусложненными и трудными для написания, а монадные трансформации являются просто уродливыми хаками. Все это очень похоже на “шаблоны проектирования” в Java. Вы заметили, как много тривиальных задач для других языков программирования становиться исследовательской проблемой при попытке реализовать их на Haskell? Постоянно выходят статьи с названием в стиле “Монадный подход к решению такой-то-уже-решенной-задачи”. Иронично, но Amr Sabry (мой консультант в PhD) является со-автором одной из таких статей. Он пытался реализовать семейство языков miniKanren Дэна Фридмена, но так и не смог понять, каким образом сконструировать монады. Он попросил помощи у Олега Киселева, человека, который возможно лучше всех разбирается в системе типов Haskell. И, если вы не знаете, Amr Sabry, вероятно, самый осведомленный в мире человек по теме чистых функциональных языков программирования. Они стали соавторами целой статьи после того, как с помощью Олега, им все-таки удалось реализовать желаемое. Ирония в том, что у Дена Фридмана не возникло вообще никаких проблем с реализацией этого куска кода на Scheme.

Некоторые люди утверждают, что значение монад в том, что они “ограждают” разработчиков от побочных эффектов. Но какой был бы толк от этого ограждения, если бы монады не делали ваш код более понятным или безопасным? А правда в том, что они не делают. Программы, написанные с применением монад так же сложно анализировать, как если бы они были напичканы побочными эффектами. Нет ничего такого, что можно сделать проще с помощью монад, и что не может быть сделано путем статического анализа. Любой исследователь статического анализа знает об этом. По существу, статические анализаторы кода основаны целиком на монадах, и тем самым они отбирают у программистов бремя по написанию монадического кода, вместо того, чтобы перекладывать его на них. Конечно, злоупотребление побочными эффектами делает программный код сложным для анализа, но с другой стороны никто не мешает вам писать чистые функции даже на С:

int f(int x) {
int y = 0;
int z = 0;
y = 2 * x;
z = y + 1;
return z / 3;
}

Вы можете сделать тоже самое и на ассемблере. Чистые функции не являются чем-то таким, что относится исключительно к функциональному программированию. Вы можете создавать чистые функции на любом языке, но главное, вы должны иметь возможность также использовать побочные эффекты.

Оглядываясь назад в историю, становится понятным, что именно идеализм математиков стал движущей силой чистых функциональных языков программирования. Математические функции просты и красивы, но к сожалению они работают хорошо только тогда, когда вы пытаетесь моделировать процессы чистые по своей сути. В противном случае они становятся уродливыми. Не стоит бояться умных словечек вроде “Теория категорий”. Я знаю значительное количество различных теорий с таким названием. Даже исследователи теорий категорий порой называют их “абстрактной чепухой”, потому что по сути они занимаются лишь тем, что находят очередной нелепый способ сказать о том, что вы уже и так знаете! Если вы прочтете статью Готлоба Фреге “Function and concept”, то будете удивлены тому, как большинство математиков использовали функции неправильно, и это было всего лишь чуть более столетия назад. На самом деле, математики совершили много плохих вещей со своим языком, особенно в том, что касается вычислений. Так что не существует никаких оснований в том, что проектировщики языков программирования должны слепо учиться этому у математиков.

Не нужно влюбляться в ваши модели

Что-угодно начинает приносить вред, когда вы начинаете пользоваться этим слишком часто. Доходя до крайности, как ООП так и ФП пытаются втиснуть мир в свою собственную своеобразную модель, но проблема в том, что мир существует независимо от того, что мы о нем думаем. Совершенно неприемлемо пытаться думать обо всем как о гвоздях только потому, что у вас в руках молоток. И только наблюдая за реальностью мы можем избавиться от тех религиозных заблуждений, которые в действительности ограничивают нас.

Не подгоняйте весь мир под ваши модели. Приспосабливайте ваши модели для окружающего мира.
Изображение

dyvniy M
Автор темы, Администратор
Администратор
Аватара
dyvniy M
Автор темы, Администратор
Администратор
Возраст: 41
Репутация: 1
Лояльность: 1
Сообщения: 3579
Зарегистрирован: Ср, 10 октября 2012
С нами: 11 лет 5 месяцев
Профессия: Программист
Откуда: Россия, Москва
ICQ Сайт Skype ВКонтакте

#63 dyvniy » Ср, 21 марта 2018, 10:56:23

Толковое использование препроцессора boost::preprocessor
https://habrahabr.ru/post/199496/
Спойлер
RMI средствами С++ и boost.preprocessor
C++,
Программирование
Из песочницы
Это моя первая публикация на сем ресурсе, посему, прошу отнестись с пониманием к допущенным мною ошибкам.

RMI — весьма банальная задача для ЯП, поддерживающих интроспекцию. Но, С++, к сожалению, к ним не относится.

В данной публикации я хочу продемонстрировать возможность реализации весьма юзабильной RMI средствами С++ препроцессора.

Постановка задачи

1. Предоставить максимально простой синтаксис, чтоб невозможно было допустить ошибку.
2. Идентификация(связывание) процедур должна быть скрыта от пользователя для того, чтоб невозможно было допустить ошибку.
3. Синтаксис не должен накладывать ограничения на используемые С++ типы.
4. Должна присутствовать возможность версионности процедур, но, так, чтоб не ломалась совместимость с уже работающими клиентами.

Касательно четвертого пункта:
К примеру, у нас уже есть две процедуры add/div по одной версии каждой. Мы хотим для add добавить новую версию. Если просто добавить еще одну версию — у нас поплывут ID`ы процедур, о которых знают клиентские программы, собранные до внесения этого изменения.

Выбор инструмента

Т.к. конечный результат предполагается использовать совместно с С++ кодом, вариантов я вижу три:
Изобретаем синтаксис и пишем свой кодогенератор.
Используем С++ препроцессор.
Ищем нечто готовое и допиливаем под себя(если нужно).


Выскажусь о каждом из вариантов соответственно:
Зачем дополнительная стадия кодогенерации?
Препроцессор я люблю, и часто его использую.
Трата времени и сил. И, не понятно, будет ли в этом смысл.


Касательно первого, второго и третьего пунктов требований — препроцессорный вариант подходит.

Итак, выбор сделан — используем препроцессор. И да, разумеется boost.preprocessor.

Немного о препроцессоре

Типы данных С++ препроцессора:
arrays: (size, (elems, ....))
lists: (a, (b, (c, BOOST_PP_NIL)))
sequences: (a)(b)(c)
tuples: (a, b, c)

Типов, как видно, более чем достаточно.
Немного подумав, почитав про возможности и ограничения каждого их них, а также учтя желаемую простоту синтаксиса и невозможность допустить ошибку — выбор был сделан в пользу sequences и tuples.

Несколько поясняющих примеров.
(a)(b)(c) — sequence. Тут, мы описали sequence, состоящий из трех элементов.
(a) — также sequence, но состоящий из одного элемента. (внимание!)
(a)(b, c)(d, e, f) — снова sequence, но состоящий из трех tuples. (обратите внимание на первый элемент — уловочка, однако, но это и правда tuple)
(a)(b, c)(d, (e, f)) — опять же sequence, и так же состоящий из трех tuples. Но! Последний tuple состоит их двух элементов: 1) любого элемента, 2) tuple.
И, напоследок, такой пример: (a)(b, c)(d, (e, (f)(g))) — тут уж разберитесь сами ;)
Как видно, все и вправду нереально просто.

Прототипируем синтаксис

После недолгого раздумья, вырисовывается такой синтаксис:
(proc_name0, // имя процедуры
(signature_arg0, signature_arg1, signature_argN) // первая версия процедуры
(signature_arg0) // вторая версия процедуры
)
(proc_name1, // имя процедуры
(signature_arg0, signature_arg1) // единственная версия этой процедуры
)
(proc_name2, // имя процедуры
() // единственная версия этой процедуры (без аргументов)
)

Ну… весьма употрибительно, однако.

Некоторые детали реализации

Т.к. одно из требований — версионность процедур, да еще и такая, чтоб не ломалась совместимость с уже существующими клиентами — нам, для идентификации процедур, понадобятся два ID`а. Первый — ID процедуры, второй — ID версии.

Поясню на примере.
Допустим, это описание API нашего сервиса. Допустим, у нас уже есть клиентские программы, использующие этот API.
(proc_name0, // procID=0
(signature_arg0, signature_arg1) // sigID=0
)
(proc_name1, // procID=1
(signature_arg0, signature_arg1) // sigID=0
)
(proc_name2, // procID=2
() // sigID=0
)

Теперь, для proc_name0() нам нужно добавить еще одну версию с другой сигнатурой.
(proc_name0, // procID=0
(signature_arg0, signature_arg1) // sigID=0
(signature_arg0, signature_arg1, signature_arg2) // sigID=1
)
(proc_name1, // procID=1
(signature_arg0, signature_arg1) // sigID=0
)
(proc_name2, // procID=2
() // sigID=0
)

Таким образом, у нас появился новый ID версии процедуры, в то время как прежний остался без изменений.
Было: (0:0), стало: (0:0)(0:1)
Т.е. именно этого мы и пытались добиться. Прежние клиенты как использовали (0:0), так и далее будут использовать эти идентификаторы, не переживая о том, что появились новые версии этих процедур.
Также условимся в том, что все новые процедуры нужно добавлять в конец.

Далее, нам нужно позаботиться о том, чтоб ID`ы автоматически проставлялись на обоих сторонах сервиса. Запросто! — просто используем одну и ту же описанную последовательность дважды, для генерации клиентской и серверной сторон!

Самое время представить, как мы хотим видеть все это в конечном счете:
MACRO(
client_invoker, // name of the client invoker implementation class
((registration, // procedure name
((std::string, std::string)) // message : registration key
))
((activation,
((std::string)) // message
))
((login,
((std::string)) // message
))
((logout,
((std::string)) // message
))
((users_online,
((std::vector<std::string>)) // without args
))
,
server_invoker, // name of the server invoker implementation class
((registration,
((std::string)) // username
))
((activation,
((std::string, std::string, std::string)) // registration key : username : password
))
((login,
((std::string, std::string)) // username : password
))
((logout,
(()) // without args
))
((users_online,
(()) // without args
((std::string)) // substring
))
)

Чтоб не было путаницы в том, кто ведущий, а кто ведомый — условимся так, что процедуры, описываемые на одной из сторон, являются реализациями, находящимися на противоположной стороне. Т.е., к примеру, client_invoker::registration(std::string, std::string) говорит нам о том, что реализация этой процедуры будет находиться на стороне сервера, в то время как интерфейс к этой процедуре будет находиться на стороне клиента, и наоборот.
(двойные круглые скобки мы используем потому, что препроцессор при формировании аргумента для нашего MACRO(), развернет нами описанное API. это можно побороть, но не знаю, нужно ли?..)

Итог

Из приведенного выше макровызова, будет сгенерирован код, находящийся под спойлером.
Код
(в качестве сериализации используется другой мой проект — YAS)

Как бонус, была добавлена системная процедура yarmi_error() — используется для сообщения противоположной стороне о том, что при попытке произвести вызов произошла ошибка. Посмотрите внимательно, в client_invoker::invoke(), десериализация и вызов обернуты в try{}catch() блок, а в catch() блоках производится вызов yarmi_error(). Таким образом, если при десериализации или вызове процедуры возникнет исключение — оно будет успешно перехвачено catch() блоком, и информация об исключении будет отправлена вызывающей стороне. То же самое будет происходить и в противоположном направлении. Т.е. если сервер вызвал у клиента процедуру, в ходе вызова которой возникло исключение — клиент отправит серверу информацию об ошибке, также дополнительно сообщив ID и версию вызова, в котором возникло исключение. Но, использовать yarmi_error() вы можете и сами, ничто этого не запрещает. Пример: yarmi_error(call_id, version_id, "message");

Как вы могли заметить, к именам описанных нами процедур, на стороне их реализации добавляется префикс on_

Классы client_invoker и server_invoker принимают два параметра. Первый их низ — класс, в котором реализованы вызываемые процедуры, второй — класс, в котором реализован метод send(yas::shared_buffer buf).
Если у вас один и тот же класс выполняет обе роли, вы можете сделать так:
struct client_session: yarmi::client_base<client_session>, yarmi::client_invoker<client_session> {
client_session(boost::asio::io_service &ios)
:yarmi::client_base<client_session>(ios, *this)
,yarmi::client_invoker<client_session>(*this, *this) // <<<<<<<<<<<<<<<<<<<<<<<<<
{}
};

Конечный вариант выглядит так:
struct client_session: yarmi::client_base<client_session>, yarmi::client_invoker<client_session> {
client_session(boost::asio::io_service &ios)
:yarmi::client_base<client_session>(ios, *this)
,yarmi::client_invoker<client_session>(*this, *this)
{}

void on_registration(const std::string &msg, const std::string ®key) {}
void on_activation(const std::string &msg) {}
void on_login(const std::string &msg) {}
void on_logout(const std::string &msg) {}
void on_users_online(const std::vector<std::string> &users) {}
};


Интерфейс к противоположной стороне будет унаследован из предка yarmi::client_invoker. Т.е., к примеру, будучи в конструкторе нашего client_session, вы можете вызвать процедуру registration() следующим образом:
{
registration("niXman");
}

Ответ мы получим в нашу реализацию client_session::on_registration(std::string msg, std::string regkey)
Всё!

Из недоделок нужно отметить следующую:
В именах типов, описывающих процедуры, нельзя использовать запятые, ибо препроцессор не понимает контекста, в котором они используются. Будет исправлено.

В конечном счете, все это вылилось в проект под названием YARMI(Yet Another RMI).
Описанный кодогенератор закодирован в одном файле — yarmi.hpp. В сумме, на реализацию кодогенератора ушел один рабочий день.

Пример использования всего этого дела можно увидеть тут и тут. Первый тестовый проект все еще не завершен, к сожалению.

В дополнение к описанному, на странице проекта вы найдете коды асинхронного многопользовательского однопоточного сервера, и коды клинента.

Вместо заключения

Планы:
1. Генерация нескольких интерфейсов
2. Описать спецификацию (хоть она и проще некуда)
3. Возможность использовать собственный алокатор

Буду благодарен конструктивной критике и предложениям.

PS
Этот код используется в нескольких наших коммерческих проектах, в геймдеве.
Изображение

dyvniy M
Автор темы, Администратор
Администратор
Аватара
dyvniy M
Автор темы, Администратор
Администратор
Возраст: 41
Репутация: 1
Лояльность: 1
Сообщения: 3579
Зарегистрирован: Ср, 10 октября 2012
С нами: 11 лет 5 месяцев
Профессия: Программист
Откуда: Россия, Москва
ICQ Сайт Skype ВКонтакте

#64 dyvniy » Ср, 21 марта 2018, 16:30:08

Изображение

dyvniy M
Автор темы, Администратор
Администратор
Аватара
dyvniy M
Автор темы, Администратор
Администратор
Возраст: 41
Репутация: 1
Лояльность: 1
Сообщения: 3579
Зарегистрирован: Ср, 10 октября 2012
С нами: 11 лет 5 месяцев
Профессия: Программист
Откуда: Россия, Москва
ICQ Сайт Skype ВКонтакте

#65 dyvniy » Ср, 4 апреля 2018, 10:48:33

Про сложность алгоритмов
https://habrahabr.ru/post/188010/
Изображение

dyvniy M
Автор темы, Администратор
Администратор
Аватара
dyvniy M
Автор темы, Администратор
Администратор
Возраст: 41
Репутация: 1
Лояльность: 1
Сообщения: 3579
Зарегистрирован: Ср, 10 октября 2012
С нами: 11 лет 5 месяцев
Профессия: Программист
Откуда: Россия, Москва
ICQ Сайт Skype ВКонтакте

#66 dyvniy » Пн, 4 июня 2018, 17:30:27

Что нужно знать
https://vk.com/@itcookies-kakie-algoritmy-nuzhno- ... toby-stat-horoshim-programmist
algorithms
Какие алгоритмы нужно знать, чтобы стать хорошим программистом?
Данная статья содержит не только самые распространенные алгоритмы и структуры данных, но и более сложные вещи, о которых вы могли не знать. Читаем и узнаем!

Я предполагаю, что вы знаете как минимум один язык программирования и такие понятия, как объект и указатель. Алгоритмы и структуры данных будут перечисляться по степени их сложности.

Для начала давайте начнем с линейных структур данных и алгоритмов
Массивы
Связный список
Стек
Очереди

Перейдем к базовым алгоритмам
Сортировка — Сортировка слиянием, Сортировка вставками, Быстрая сортировка, Несколько взаимных перестановок.
Умножение матриц (Не обязательно реализовывать, главное — знать алгоритм)
Основные алгоритмы просеивания
Беззнаковая математика, включая умножение и деление
Алгоритм Евклида для нахождения НОД (наибольший общий делитель), Модульная инверсия, Быстрое возведение в степень
Числа Фибоначчи с матричным умножением
Нормальное распределение и математическое ожидание
Статистика – среднее вероятностное значение случайной величины, медиана, дисперсия, теорема Байеса
Также можно изучить популярные алгоритмические методы:
Алгоритмы декомпозиции – Бинарный поиск, Нахождение подмассива с наибольшей суммой элементов
Жадные алгоритмы – Выбор задач, кодирование по алгоритму Хаффмана
Динамичное программирование – Цепное матричное умножение, Алгоритм решения задачи по укладке ранца
Линейное программирование – Максимизация параметра, Линейное время сортировки
Криптографические алгоритмы – Алгоритм Манакера по нахождению длиннейшей подстроки-палиндрома, алгоритм нахождения наибольшей общей подпоследовательности (LSC), расстояние Левенштейна
Теперь перейдем к типичным нелинейным структурам данных
Деревья – Бинарное дерево, Дерево общего вида, Наименьший общий предок
Бинарное дерево поиска – Симметричный обход, Обход по уровням, Нахождение k’ого наибольшего элемента, Диаметр, Глубина, Количество узлов и т.д.
Динамическая память – Динамический массив, Двоичная куча, Пирамидальная сортировка
Алгоритм объединения-поиска
Хеш-таблица – Метод нахождения коллизий (Linear Probing), Открытая адресация, Предотвращение коллизий
Рассмотрим графы
Список смежных вершин графа, Матрица смежности графа, Взвешенные рёбра графа
Основные алгоритмы обхода – Поиск в ширину, Поиск в глубину и т.д.
Алгоритмы нахождения кратчайшего пути — Алгоритм Дейкстры, Алгоритм Флойда-Уоршелла, Алгоритм Беллмана-Форда
Минимальное остовное дерево — Алгоритм Крускала, Алгоритм Прима
К данному моменту вы должны быть хорошо знакомы с программированием, так как для дальнейшего прочтения и углубления в данную тему вы должны знать больше, чем студент.

Усложнённые деревья и графы
Сбалансированные деревья – AVL-дерево, Красно-черное дерево
Heavy-light декомпозиция, Б-деревья, Дерево квадрантов
Усложнённый граф – Минимальный разрез, Максимальный поток
Максимальное покрытие – Теорема о свадьбах
Гамильтонов цикл
Рёберный граф/ Линейный граф
Сильно связные компоненты
Главный подграф, Покрытие вершин, Задача коммивояжёра – Алгоритм аппроксимации
Продвинутые криптографические алгоритмы:
Алгоритм Кнута-Морриса-Пратта
Алгоритм Рабина-Карпа
Префиксные и суффиксные деревья
Автоматизация суффиксов – Алгоритм Укконена
Высшая математика
Быстрое преобразование Фурье
Проверка простоты
Вычислительная геометрия – Задача поиска ближайшей пары, Диаграмма Вороного, Выпуклая оболочка множества точек
Общие продвинутые темы:
Выполнение обхода всех комбинаций/перестановок
Поразрядная обработка
Изображение


Название раздела: Программирование (под Desktop и Android)
Описание: Разработка и отладка приложений. Упор на 3D-графику.

Быстрый ответ


Введите код в точности так, как вы его видите. Регистр символов не имеет значения.
Код подтверждения
:) ;) :hihi: :P :hah: :haha: :angel: :( :st: :_( :cool: 8-| :beee: :ham: :rrr: :grr: :* :secret: :stupid: :music: Ещё смайлики…
   

Вернуться в «Программирование (под Desktop и Android)»

Кто сейчас на форуме (по активности за 15 минут)

Сейчас этот раздел просматривают: 9 гостей