.equals()
сравнивает данные в классе (при условии, что функция реализована). ==
сравнивает местоположения указателя (расположение объекта в памяти).
==
возвращает true, если оба объекта (NOT TALKING OF PRIMITIVES) указывают на экземпляр SAME. .equals()
возвращает true, если два объекта содержат одни и те же данные equals()
Versus ==
в Java
Это может вам помочь.
Два больших аргумента в пользу этого типа typedef
- это сокращенная типизация, о которой вы уже упоминали, и простота перехода на новый тип контейнера. FoobarList может поддерживаться вектором
, списком
или двухсторонней очередью
, и переключение часто просто требует изменения typedef.
Ваша неприязнь к ним, когда дело доходит до их поиска, значительно уменьшается при работе с IDE, поскольку я могу просто навести курсор на имя типа, и IDE сообщает мне, как он определяется.
Более полезные ситуации возникают, когда у вас есть вложенные контейнеры - вы можете дать именам некоторое семантическое значение, не определяя целые классы:
typedef std::list<Foobar> FoobarList;
typedef std::map <string, FoobarList> GizmosToFoobarsMap;
Вы также можете сэкономить МНОГО печатания при работе с итераторами этих типов (хотя это уменьшено теперь, когда C ++ 0x имеет auto
.)
typedef
важны для STL и программирования шаблонов в целом. Просто посмотрите, как работают черты итератора:
template <class Iterator>
struct iterator_traits {
typedef typename Iterator::iterator_category iterator_category;
typedef typename Iterator::value_type value_type;
typedef typename Iterator::difference_type difference_type;
typedef typename Iterator::pointer pointer;
typedef typename Iterator::reference reference;
};
template <class T>
struct iterator_traits<T*> {
typedef random_access_iterator_tag iterator_category;
typedef T value_type;
typedef ptrdiff_t difference_type;
typedef T* pointer;
typedef T& reference;
};
Что касается «сокращенного» использования typedef
s - я думаю, что это прекрасно, когда оно локализовано в файле реализации. У меня здесь то же правило, что и для с использованием пространства имен
- если это избавляет меня от набора текста и не сбивает с толку других, действуйте.
Мне нравятся такие определения типов, потому что они улучшают читаемость. Рассмотрите возможность использования std :: vector
повсюду или даже std :: map
. Половина вашего кода заканчивается просто описанием типов, и набор определений типов значительно упрощает это. Я не думаю, что это слишком сложно для понимания - IDE обычно могут сказать вам тип, если вы наводите на него указатель мыши или переходите к определению, а работа с кодом обычно требует, чтобы вы все равно понимали такие вещи.
Мне нравится этот typedef
, потому что он позволяет изменить тип контейнера позже (скажем, профилирование показало, что std :: deque
быстрее, чем std :: list
) , без изменения всего рассматриваемого кода .
И на случай, если вы задаетесь вопросом: я не возражаю против того, чтобы то, что называется списком, даже если это может быть std :: deque
. В этом контексте я рассматриваю «список» как концептуальный термин, а не как структуру данных. (Вы ведь не составляете списки покупок как двусвязные?)
Я лично считаю, что определения типов очень полезны при работе с шаблонным кодом, как при написании шаблонов, так и при их создании.
Написание шаблонов:
typedef требуется для метапрограммирования шаблонов. Например, удаление const:
template<typename T>
struct RemoveConst
{
typedef T Type;
};
template<>
struct RemoveConst<const T>
{
typedef T Type;
};
Теперь мы можем получить доступ к типу без констант из любого T путем создания экземпляра RemoveConst :: Type
Создание экземпляров шаблонов:
Как и многое в программировании, правильный инструмент для правильного job. typedef, такой как ваш пример
typedef std::list<Foobar> FoobarList;
...
FoobarList GetFoobars();
Звучит вполне разумно, главным образом потому, что определенное типом имя является описательным (FoobarList - это список Foobars). Это очень полезно, особенно при работе с контейнерами STL или любыми другими типами шаблонов, которые используют один и тот же интерфейс. представьте себе следующее объявление класса:
class SomeClass
{
...
std::vector<int> mContainer;
};
Этот класс, вероятно, будет перебирать элементы контейнера, что приведет к получению кода, подобного:
for(std::vector<int>::iterator It = mContainer.begin(); It != mContainer.end(); ++It)
{
}
Теперь представьте, что вы понимаете, после написания вышеупомянутого цикла for 5 различными методами, что вы постоянно вставляете в середину массива, и что std :: list намного лучше подходит для этой работы. В этом случае вам придется пройти через все экземпляры std :: vector :: iterator и измените объявление.
Вместо этого вы можете определить тип контейнера, который вы используете внутри класса:
class SomeClass
{
typedef std::vector<int> IntContainer;
...
IntContainer mContainer;
};
Это может позволить вам написать очень общий код, который можно очень легко изменить.
for(IntContainer::iterator It = mContainer.begin(); It != mContainer.end(); ++It)
{
}
В этом случае вам нужно только изменить typedef для IntContainer, и любой экземпляр, который ссылается на него, будет автоматически изменен.
Вы часто видите определения типов, когда вам нужно написать код C ++, который работает на разных платформах, потому что размер основных типов данных не фиксирован. Например, виртуальная машина Java имеет определения типов для основных типов для разных компиляторов.
Я думаю, что в этом случае хорошей практикой является typedef. Хотя синтаксис шаблона ставит параметры перед вами, обычно они представляют собой те вещи, которые можно попытаться инкапсулировать в объектно-ориентированной среде.
typedef'ing шаблона выполняет эту инкапсуляцию.
В вашем примере FoobarList
не отличается от любого другого класса; вам все равно придется посмотреть определение или документацию, чтобы знать, как его использовать; и вы не стали бы использовать это как аргумент в пользу отказа от классов!
В этом случае, как только вы определили, что это STL std :: list, вы будете знать, как его использовать или, по крайней мере, где найти документацию хорошего качества; что, вероятно, больше, чем вы можете сказать для большинства классов, которые могут быть определены в вашем проекте.
При этом я неоднозначно отношусь к тому, хорошая это идея или нет; Я просто считаю, что это в основном безвредно.
Лично я также typedef
материал STL, список и итератор ( Foos
и FooIterator
). Используемый повсюду создает очень четкий (imo) стиль, к которому вы привыкаете ко второму случаю.
Кроме того, вы можете просто навести указатель мыши на тип, чтобы увидеть, что он на самом деле означает в любой реальной среде IDE (vs или eclipse), так что у вас будет вся необходимая информация прямо в момент ее использования в дополнение к тому, что вам не нужно прокручивать 3 экрана. вправо, чтобы прочитать всю строку.
С ключевым словом C ++ 0x auto
это больше не понадобится. Да, я верю в ключевое слово C # var
, и снова вы можете навести на него курсор, чтобы увидеть тип, если сомневаетесь!
Я тоже не делаю этого в своем коде, так что, думаю, вы не одиноки. На мой взгляд, это заставляет читателя искать еще одну вещь. У плохого читателя кода их уже более чем достаточно.
Если я хочу, чтобы конечный тип был строго типизирован, я обычно предпочитаю наследование, а не typedef , например,
class CMyMap: public CMap
Таким образом, CMyMap строго типизирован - я просто не могу передать CMap
в метод, который принимает CMyMap в качестве параметра. typedef просто сообщает компилятору «другое имя для», но определение класса определяет новый тип.
Если я определил функцию void foo (const CMyMap & map);
, но я использовал typedef для определения CMyMap, например
typdef CMap
тогда кто-то может сделать
CMap
foo (myMap);
и компилятор быть вполне счастливым.
Но если я действительно хочу, чтобы foo был строго типизирован и использовал ТОЛЬКО CMyMap, то для «типизации» я предпочитаю наследование.
Кстати, CMyMap является своего рода общим, но более конкретным может быть CPhoneNumber2Index, где номер телефона представлен целым числом, а индекс - целым числом. Здесь очевидно, что мой CPhoneNumber2Index - это не просто отображение целого числа на другое целое число. Таким образом, foo может быть
строкой ReverseLookup (const CPhoneNumber2Index & PhoneBook, int iPhoneNumber);
, что показывает, что ReverseLookup имеет какое-то отношение к телефонным номерам, а не CMap
После тяжелой возни с шаблонами, typedef ведет себя как обычное присвоение переменных...