Varsayılan değeri belirtmek için bir yolu var mı std::map
'ın operator[]
bir anahtar yoksa zaman döner?
Yanıtlar:
Hayır yok. En basit çözüm, bunu yapmak için kendi ücretsiz şablon işlevinizi yazmaktır. Gibi bir şey:
#include <string>
#include <map>
using namespace std;
template <typename K, typename V>
V GetWithDef(const std::map <K,V> & m, const K & key, const V & defval ) {
typename std::map<K,V>::const_iterator it = m.find( key );
if ( it == m.end() ) {
return defval;
}
else {
return it->second;
}
}
int main() {
map <string,int> x;
...
int i = GetWithDef( x, string("foo"), 42 );
}
C ++ 11 Güncellemesi
Amaç: Genel ilişkilendirici kapsayıcıların yanı sıra isteğe bağlı karşılaştırıcı ve ayırıcı parametrelerini hesaba katın.
template <template<class,class,class...> class C, typename K, typename V, typename... Args>
V GetWithDef(const C<K,V,Args...>& m, K const& key, const V & defval)
{
typename C<K,V,Args...>::const_iterator it = m.find( key );
if (it == m.end())
return defval;
return it->second;
}
operator[]
varsayılan değerle tam olarak aynı davranışı sağlamak için , varsayılan değer if ( it == m.end() )
bloğun içindeki haritaya eklenmelidir
Bu, soruyu tam olarak yanıtlamasa da, aşağıdaki gibi bir kodla sorunu çözdüm:
struct IntDefaultedToMinusOne
{
int i = -1;
};
std::map<std::string, IntDefaultedToMinusOne > mymap;
C ++ standardı (23.3.1.2), yeni eklenen değerin varsayılan olarak oluşturulduğunu belirtir, bu nedenle map
kendisi bunu yapmanın bir yolunu sağlamaz. Seçimleriniz:
operator[]
bu varsayılanı eklemek için uygulayan kendi sınıfınıza sarın .C ++ 17, try_emplace
tam olarak bunu yapan sağlar. Değer oluşturucu için bir anahtar ve bir bağımsız değişken listesi alır ve bir çift döndürür: an iterator
ve a bool
.: Http://en.cppreference.com/w/cpp/container/map/try_emplace
Daha Genel Sürüm, Destek C ++ 98/03 ve Daha Fazla Konteyner
Genel ilişkisel kapsayıcılarla çalışır, tek şablon parametresi kap türünün kendisidir.
: Desteklenen kaplar std::map
, std::multimap
, std::unordered_map
, std::unordered_multimap
, wxHashMap
, QMap
, QMultiMap
, QHash
, QMultiHash
, vb
template<typename MAP>
const typename MAP::mapped_type& get_with_default(const MAP& m,
const typename MAP::key_type& key,
const typename MAP::mapped_type& defval)
{
typename MAP::const_iterator it = m.find(key);
if (it == m.end())
return defval;
return it->second;
}
Kullanım:
std::map<int, std::string> t;
t[1] = "one";
string s = get_with_default(t, 2, "unknown");
Burada yönteme çok benzer olan bir sarmalayıcı sınıf kullanarak benzer uygulamasıdır get()
arasında dict
Python'da türü https://github.com/hltj/wxMEdit/blob/master/src/xm/xm_utils.hpp
template<typename MAP>
struct map_wrapper
{
typedef typename MAP::key_type K;
typedef typename MAP::mapped_type V;
typedef typename MAP::const_iterator CIT;
map_wrapper(const MAP& m) :m_map(m) {}
const V& get(const K& key, const V& default_val) const
{
CIT it = m_map.find(key);
if (it == m_map.end())
return default_val;
return it->second;
}
private:
const MAP& m_map;
};
template<typename MAP>
map_wrapper<MAP> wrap_map(const MAP& m)
{
return map_wrapper<MAP>(m);
}
Kullanım:
std::map<int, std::string> t;
t[1] = "one";
string s = wrap_map(t).get(2, "unknown");
Varsayılan değeri belirlemenin bir yolu yoktur - her zaman varsayılan tarafından oluşturulan değerdir (sıfır parametre yapıcısı).
Aslında operator[]
, haritada verilen anahtar için bir değer yoksa, varsayılan kurucudan gelen değerle yeni bir değer ekleyeceği gibi muhtemelen beklediğinizden fazlasını yapar.
find
için, belirli bir anahtar için herhangi bir öğe yoksa son yineleyiciyi döndüren kullanabilirsiniz .
find
Bu durumda zaman karmaşıklığı ne kadar ?
template<typename T, T X>
struct Default {
Default () : val(T(X)) {}
Default (T const & val) : val(val) {}
operator T & () { return val; }
operator T const & () const { return val; }
T val;
};
<...>
std::map<KeyType, Default<ValueType, DefaultValue> > mapping;
Değer, diğer yanıtların söylediği gibi varsayılan kurucu kullanılarak başlatılır. Bununla birlikte, basit türler (int, float, pointer veya POD (eski verileri planla) türleri gibi integral türleri) durumunda, değerlerin sıfır olarak başlatıldığını (veya değer başlatma ile sıfırlandığını (etkin bir şekilde) eklemek yararlıdır. aynı şey), hangi C ++ sürümünün kullanıldığına bağlı olarak).
Her neyse, alt satırda, basit türlere sahip haritaların yeni öğeleri otomatik olarak sıfırlayacağıdır. Bu nedenle bazı durumlarda, varsayılan başlangıç değerini açıkça belirtme konusunda endişelenmenize gerek yoktur.
std::map<int, char*> map;
typedef char *P;
char *p = map[123],
*p1 = P(); // map uses the same construct inside, causes zero-initialization
assert(!p && !p1); // both will be 0
Tür adından sonraki parantezler yeniyle bir fark yaratır mı? Konusuna bakın. konuyla ilgili daha fazla ayrıntı için.
Bir geçici çözüm map::at()
yerine kullanmaktır []
. Anahtar yoksa,at
bir istisna atar. Daha da güzel, bu vektörler için de işe yarar ve bu nedenle haritayı bir vektörle değiştirebileceğiniz genel programlama için uygundur.
Kayıtlı olmayan anahtar için özel bir değer kullanmak tehlikeli olabilir, çünkü bu özel değer (-1 gibi) kodda daha aşağı işlenebilir. İstisnalar dışında, hataları tespit etmek daha kolaydır.
Belki istediğiniz varsayılan bir değerle tahsis eden özel bir ayırıcı verebilirsiniz.
template < class Key, class T, class Compare = less<Key>,
class Allocator = allocator<pair<const Key,T> > > class map;
operator[]
T()
ayırıcı ne yaparsa yapsın, çağırarak oluşturulan bir nesne döndürür .
construct
yöntemini çağırmıyor mu ? Bunu değiştirmek mümkün olabilir diye düşünüyorum. Yine de, iyi biçimlenmemiş construct
dışında bir şey yapan bir işlevden şüpheleniyorum new(p) T(t);
. DÜZENLEME: Geriye dönüp baktığımda aptalca, aksi halde tüm değerler aynı olurdu: P
operator[]
geri dönen diyor (*((insert(make_pair(x, T()))).first)).second
. Yani bir şeyi kaçırmıyorsam, bu cevap yanlış.
insert
bir ile T()
, ama yeni bir için ayırıcı olsun bellek kullanır zaman iç dolgudur T
sonra çağrı construct
olduğunu da verildi parametresi, o bellek T()
. Dolayısıyla, davranışını operator[]
başka bir şeye döndürmek için değiştirmek gerçekten mümkündür , ancak ayırıcı neden çağrıldığını ayırt edemez. Bu nedenle construct
, parametresini göz ardı etsek ve özel değerimizi kullansak bile , bu, inşa edilen her elemanın bu değere sahip olduğu anlamına gelir ki bu kötü.
Yanıta Genişleyen https://stackoverflow.com/a/2333816/272642 bu şablon işlevini kullanır std::map
'ler key_type
ve mapped_type
typedefs tipini anlamak için key
ve def
. Bu, bu tür yazıların olmadığı kaplarla çalışmaz.
template <typename C>
typename C::mapped_type getWithDefault(const C& m, const typename C::key_type& key, const typename C::mapped_type& def) {
typename C::const_iterator it = m.find(key);
if (it == m.end())
return def;
return it->second;
}
Bu, kullanmanıza izin verir
std::map<std::string, int*> m;
int* v = getWithDefault(m, "a", NULL);
gibi argümanlar atmaya gerek kalmadan std::string("a"), (int*) NULL
.
Kullanım std::map::insert()
.
Bu partiye oldukça geç kaldığımı fark ederek, ancak operator[]
özel varsayılanların davranışıyla ilgileniyorsanız (yani, verilen anahtara sahip öğeyi bulun, yoksa haritaya bir öğe ekleyin varsayılan değeri seçer ve yeni eklenen değere veya mevcut değere bir başvuru döndürür), C ++ 17 öncesi size sunulan bir işlev zaten var std::map::insert()
. insert
anahtar zaten mevcutsa gerçekte eklenmez, bunun yerine mevcut değere bir yineleyici döndürür.
Diyelim ki, dizeden int'e bir eşleme istediniz ve anahtar henüz mevcut değilse varsayılan bir 42 değeri ekler:
std::map<std::string, int> answers;
int count_answers( const std::string &question)
{
auto &value = answers.insert( {question, 42}).first->second;
return value++;
}
int main() {
std::cout << count_answers( "Life, the universe and everything") << '\n';
std::cout << count_answers( "Life, the universe and everything") << '\n';
std::cout << count_answers( "Life, the universe and everything") << '\n';
return 0;
}
42, 43 ve 44 çıktı vermelidir.
Harita değerini oluşturmanın maliyeti yüksekse (anahtarın kopyalanması / taşınması veya değer türünün pahalı olması durumunda), bu C ++ 17'lerle atlatılacağını düşündüğüm önemli bir performans cezasına neden olur try_emplace
.