Ответ 1
Неподписанные типы имеют три характеристики, один из которых качественно "хорош" и один из которых качественно "плох":
- Они могут содержать в два раза больше значений, чем тип подписанного типа (хороший)
- Версия
size_t
(то есть 32-разрядная на 32-разрядной машине, 64-разрядная на 64-разрядной машине и т.д.) полезна для представления памяти (адреса, размеры и т.д.) (нейтральная) - Они обходятся ниже 0, поэтому вычитание 1 в цикле или использование -1 для представления недопустимого индекса может привести к ошибкам (bad.) Подписанные типы wrap тоже.
STL использует неподписанные типы из-за первых двух пунктов выше: чтобы не ограничивать потенциальный размер подобных массиву классов, таких как vector
и deque
(хотя вы должны задать вопрос, как часто вам нужно 4294967296 элементы в структуре данных); потому что отрицательное значение никогда не будет действительным индексом для большинства структур данных; и потому что size_t
- это правильный тип, используемый для представления чего-либо, связанного с памятью, такого как размер структуры и связанные с ней вещи, такие как длина строки (см. ниже). Это не обязательно хорошая причина для использования это для индексов или других целей без памяти, таких как переменная цикла. Причина, по которой это лучше всего делать на С++, выглядит как обратная конструкция, потому что то, что используется в контейнерах, а также другие методы, и когда-то использовало остальную часть кода, должно совпадать, чтобы избежать той же проблемы, с которой вы сталкиваетесь.
Вы должны использовать подписанный тип, когда значение может стать отрицательным.
Вы должны использовать неподписанный тип, когда значение не может стать отрицательным (возможно, это не так).
Вы должны использовать size_t
при обработке размеров памяти (результат sizeof
, часто такие вещи, как длина строк и т.д.). Он часто выбирается как используемый по умолчанию тип без знака, потому что он соответствует платформе, с которой компилируется код. Например, длина строки size_t
, потому что в строке может быть только 0 или более элементов, и нет оснований ограничивать метод длины строки, произвольно меньший, чем то, что может быть представлено на платформе, например, 16 (0-65535) на 32-битной платформе. Примечание (спасибо комментатору Morwen) std::intptr_t
или std::uintptr_t
, которые концептуально похожи - всегда будут подходящего размера для вашей платформы - и должны использоваться для памяти адреса, если вы хотите что-то, что не указатель. Примечание 2 (спасибо комментатору rubenvb), что строка может содержать только теги size_t-1
из-за значения npos
. Подробности ниже.
Это означает, что если вы используете -1 для представления недопустимого значения, вы должны использовать целые числа со знаком. Если вы используете цикл для итерации по сравнению с вашими данными, вам следует использовать целое число со знаком, если вы не уверены, что конструкция цикла верна (и, как указано в одном из других ответов, они легко ошибаются). IMO, вы должны не прибегать к трюкам, чтобы обеспечить работу кода - если код требует трюков, что часто является сигналом опасности. Кроме того, это будет сложнее понять для тех, кто следит за вами и читает ваш код. Оба эти причины не следует следовать @Jasmin Gray ответ выше.
итераторы
Однако использование целых циклов для итерации по содержимому структуры данных - это неправильный способ сделать это на С++, поэтому в некотором смысле аргумент над подписанными vs без знака для циклов является спорным. Вместо этого вы должны использовать итератор:
std::vector<foo> bar;
for (std::vector<foo>::const_iterator it = bar.begin(); it != bar.end(); ++it) {
// Access using *it or it->, e.g.:
const foo & a = *it;
Когда вы это сделаете, вам не нужно беспокоиться о приведениях, подписке и т.д.
Итераторы могут быть вперёд (как указано выше) или наоборот, для итерации назад. Используйте тот же синтаксис it != bar.end()
, потому что end()
сигнализирует о завершении итерации, а не о конце базового концептуального массива, дерева или другой структуры.
Другими словами, ответ на ваш вопрос "Должен ли я использовать int или unsigned int при работе с контейнерами STL?" "Ничего. Вместо этого используйте итераторы. Узнайте больше о:
- Зачем использовать итераторы вместо индексов массива в С++?
- Почему снова (еще несколько интересных вопросов в ответах на этот вопрос)
- Итераторы в целом - разные типы, способы их использования и т.д.
Что осталось?
Если вы не используете целочисленный тип для циклов, что осталось? Ваши собственные значения, зависящие от ваших данных, но которые в вашем случае включают использование -1 для недопустимого значения. Это просто. Использовать подписанный. Просто будьте последовательны.
Я очень верю в использование естественных типов, таких как перечисления и целые числа со знаком. Они более точно соответствуют нашим концептуальным ожиданиям. Когда ваш ум и код выровнены, вы с меньшей вероятностью напишите багги-код и, скорее всего, выразительно напишите правильный, чистый код.