- 01
- 02
- 03
- 04
- 05
- 06
- 07
- 08
- 09
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
#include <iostream>
#include <string>
int main()
{
std::string s1 = "";
const char s2[] = "";
std::cout << std::boolalpha
<< std::empty(s1) << std::endl
<< std::size(s1) << std::endl
<< std::empty(s2) << std::endl
<< std::size(s2) << std::endl;
s1.assign("", 1);
std::cout << std::empty(s1) << std::endl
<< std::size(s1) << std::endl;
system("pause");
return 0;
}
У std::string есть поле с длиной и она может хранить нолики посреди строки.
Но строковые литералы в языке остались сишные, поэтому "" это в реальности массив из одного '\0'. А у std::string в конструкторе с одним параметром есть костыль -- копируются только символы до первого нолика. Иначе юзать std::string было бы неудобно.
В строке 15 заюзали другой конструктор, в который длина передаётся явно и получился std::string, который содержит один символ '\0'.
Нет чтоб сделать единый контейнер для подобного говна, и добавить б там каких-нибудь конструкторов, ну чтоб можно было там способ аллокации выбирать (если говорить про std::vector и std::array чтоб каким-то там параметром передавалось то, хотим ли мы статический массив или рантайм-вектор)
Ну а в "Modern C++" это вообще как-то некуда впихивать, в том числе из-за подобного сахара:
UPD: а для строк так вообще нужны специфические методы, типа того же find'а, split, join... - в интерфейсе вектора это всё нинужно.
Да ладно, а если мне надо последовательность из четырех флоат чисел в векторе из флоатов найти?
Ну а то, что цэпэпэшная "строка" - говно несусветное, известно уже давно и, похоже, всем.
upper/lower/swapcase должны быть не методами контейнера, а методами для типов, всунутых в контейнер (контейнеру незачем знать, что за говно в нем хранится). Ну типа можно тупым циклом или через std::for_each пробежать вектор или строку, и чтоб можно было и upper/lower/swapcase к каждому сделать (что имеет смысл для "букв") и для каждого числа abs() сделать например (что имеет смысл для знаковых чисел, но не имеет смысла для беззнаковых) или возвести в квадрат (что имеет смысл и для знаковых и для беззнаковых).
Собственно к чему я? А, да. Вот на кой хер собственно делать специальный контейнер для именно массива из "букв"? Только чтоб нолик дописать? Делать upper/lower/swapcase методом самого контейнера? Тьфу! Давайте уж тогда сделаем контейнеры отдельно для беззнаковых и знаковых чисел, и тогда только в контейнере для знаковых будет abs(), ну не охуенно ли?
Потому что компьютер ничего не знает о "буквах". Компьютер знает о байтах. Если тебе нужны "буквы", то контейнер для них нужно аккуратно спроектировать (std::string не является хорошим примером).
Один из наилучших и наиболее компактных способов — хранить строки как иммутабельные массивы байтов, представляющие кодпоинты, заэнкоженые в UTF-8.
Только сырые байты из такой строки торчать не должны. to_upper / to_lower тоже специально делать не нужно, достаточно итераторов, которые перебирают кодпоинты, и общих оптимизированных функций вида to_upper/to_lower/swapcase и прочие операции можно выразить тривиально как Модифицировать utf-8 строки in-place в общем случае нельзя (функция transliterate может менять размер контейнера).
Подытожим:
• специальный контейнер для строк — отличная идея.
• реализация std::string в плюсах — говно, потому что UTF-8 тогда ещё не придумали, и юникод был не так повсеместен.
Лучше хранить в UTF-32 - так мы избавимся от питушни, связанной с посимвольным проматыванием в итераторах, и все сведется к обычному uint32_t массиву, который можно напрямую индексировать.
Ну а если у нас мало оперативки, можно конечно и UTF-8. Только вот что насчет UTF-16, которая тоже с переменной длиной символов? А есть еще китайская кодировка GB 18030, где тоже та же хрень. К тому же в подобной хуйне в теории можно было б хранить последовательность инструкции процессора, если процессор может в инструкции переменной длины (как например в x86) чтоб их проматывать через дизассемблер длин. По-моему было б логичнее просто сделать иммутабельный кусок байт, и сделать типа специализацию чтоб туда мог приделываться итератор для проматывания UTF-8, UTF-16, GB 18030, машинного кода x86 или любой другой подобной хуйни, чем делать по контейнеру для любой хрени переменного размера в котором размер каждой хрени можно посчитать на основе значения этой самой хрени (как в UTF-8, UTF-16, GB 18030, x86 машкода и проч...)
Иногда лучше, иногда нет. Сконвертировать всегда можно при необходимости, это тривиально при наличии итератора.
> что насчет UTF-16
нинужен
> Ну а если у нас мало оперативки, можно конечно и UTF-8.
UTF-8 лучше не только для оперативки, но и для пирфоманса. Так лучше small-string optimization работать будет, больше полезных данных в кэш помещаться будет.
Более того, в оперативке твои строки никому не нужны, когда ты будешь какой-нибудь HTML отдавать, всё равно в UTF-8 конвертировать придётся, так зачем конвертить?
> По-моему было б логичнее просто сделать иммутабельный кусок байт
Кто будет помнить кодировку, царь? Пусть уж из типа будет понятно, в какой кодировке там всё лежит.
Далеко не факт. В UTF-8 найти какое-нибудь слово будет сложнее, некоторые алгоритмы поиска подстроки работают довольно хитровыебаным способом, и необходимость раздрачивать всю последовательность UTF-8 чтоб получить какой-то там символ в N-ной позиции делает эти алгоритмы попросту непрактичными, и приходится использовать более хуевые алгоритмы. То же самое касается заматчивания регулярок. UTF-8 очень хуев, когда в нем надо что-то искать.
> Кто будет помнить кодировку, царь? Пусть уж из типа будет понятно, в какой кодировке там всё лежит.
Да без проблем
typedef immutabelny_kusok_byteov utf8_string
и потом делать функции, которые принимают utf8_string в качестве аргумента и что-то там с ними делают, а если пихать этим функциям immutabelny_kusok_byteov то будет варнинги или вообще не скомпилируется
Нет, тайпдеф не так работает.
Я знаю. Но наверняка можно сделать чтоб работал (запатчить в компиляторе хрень какую-нибудь). Или тупо в struct {} обернуть.
... когда в нём надо что-то менять.
А искать нормально должно. Откатываться на запомненные позиции легко даже с utf-8.
С регулярками сложнее, но тоже можно конпелять автомат так, чтобы он прямо над байтами работал.
А оно надо? Подстрока и с байтовыми оффсетами нормально выдернется.
З.Ы. Большинству алгоритмов от строки нужны только итераторы, а не произвольный доступ к символам.
Ну т.е. один хуй пробежать по всем символам?
только ведь и UCS-32 тебе не поможет закодировать залго так, чтобы 4 * число букв совпало с числом байт (проверь)
что порекомендуешь? UCS-256? UCS-1024?
Массив из указателей на символы.
Ну вот в numpy или матлабе так и поступили: сделали контейнеры для чисел с арифметическими операциями. Поэтому учоные пользуются ими, а не сишечкой. А логи парсят питоном и перлом, а не сишечкой.
Потому что если тебе надо каждый день преобразовывать строки в верхний регистр, то удобнее для этого иметь одну функцию, а не for_each+toupper.
> строка
Если бы не костыль с c_str(), то эту хуету с чистой совестью можно было бы назвать ByteArray. Там больше ни одного специфичного для строк метода нету.
Насколько я помню, основное отличие строки от вектора в том, что содержимое строк должно быть всегда тривиально копируемо memcpy, якобы это позволяло сделать более простую и эффективную реализацию, но пруфов сходу не найду.
А для вектора из POD'ов подобная оптимизация нинужна?
--
* за исключением случая, когда QByteArray инициализируется константными данными без копирования. Если возможна такая ситуация, лучше использовать data(), который при необходимости нолик допишет.
§ 24.3.2.5 (N4659)
§ 24.3.2.7.1 (N4659)
Диапазон [0, size()] - закрытый, поэтому последний элемент у массива, возвращаемого data/c_str - charT(), что для std::string эквивалентно char() - то есть 0.
Раскрываем reference operator[](size_type pos). Поскольку !(pos < size()), это будет:
А, так оно вернет ссылку на терминатор. Все ясно.
Хитро сформулировано. А то я из описания operator[] решил, что можно было бы не хранить \0, а возвращать ссылку на какой-то статический нолик...
Чтобы ты заебался всё это учить. Ты ещё спроси, почему вместо std::vector<bool> получается какая-то невменяемая хуйня с битоёбством.
Упаковка что ли? А как тогда указатель на элемент брать?
З.Ы. Ну да, ты прав. Первая -- крестовая std::string с длиной ноль. И вторая, унаследованная от сишечки -- массив из 1 байта со значением '\0'.
Ты про const char s2[]? Ну да, это массив инициализированный сишным строковым литералом "" (т.е. одним байтом со значением '\0'). std::size() обработало его как и любой другой массив.
З.Ы. Ещё не стоит путать size и sizeof. sizeof -- размер в байтах, size -- количество элементов.
Типа умный?
Метушнёй со SFINAE.
Или, если хочется выебнуться получить сообщение об ошибке:
Ну, это их проблемы:
§ 5.13.5, 8 (N4659).
С Watcom странно, видимо, он тоже считает строковые литералы указателями.
Читать далее: https://en.cppreference.com/w/cpp/language/partial_specialization, раздел "Partial ordering".
Это что там такого MSVC в std::string накидала? Указатель на начало (8), размер (8) - а ещё что?
И, главное, как шланг смог в 4 байта?
P.S. Лол, на моей визуалке std::string весит 32 байта в релизном x64 режиме и 40 в отладочном. Ну и жирнота...
Поговорил бы кто со мной,
А то мои подружки - пиявки,да лягушки!
Фу, какая гадость!
Эх, жизнь моя - жестянка!
Да ну ее в болото!
Живу я как поганка,
А мне летааааать, а мне летааааать, а мне летать охота!
не должны быть, это просто наиболее очевидная реализация. Можно, к примеру, обойтись и одним указателем, если все данные на куче выделять:
Можно и char data[1]. Меньше аллокаций.
Спасибо, именно это я и хотел написать, а написал вместо этого херню на автомате.
Указатель сразу на буфер, немного инструкций сэкономится.
Тебе почти для многих действий нужно знать длину, всё что тут наэкономилось в трубу вылетит сразу.
Хотя, в яндексовой стандартной строке похожая оптимизация запилина
>сайт выглядит днищнее любого другого форума по программированию
http://risovach.ru/upload/2013/05/mem/neytan_20159986_orig_.jpeg
Андеграунд, хули.