Algoritma çözümü:
std::generate(numbers.begin(), numbers.end(), rand);
Menzile dayalı for-loop çözümü:
for (int& x : numbers) x = rand();
Neden std::generateC ++ 11'de aralık tabanlı for-döngüler yerine daha ayrıntılı kullanmak isteyeyim ?
Algoritma çözümü:
std::generate(numbers.begin(), numbers.end(), rand);
Menzile dayalı for-loop çözümü:
for (int& x : numbers) x = rand();
Neden std::generateC ++ 11'de aralık tabanlı for-döngüler yerine daha ayrıntılı kullanmak isteyeyim ?
begin()ve end()?
rangearaç kutusunda bir işlevi olmasını bekliyorum . (yani for(auto& x : range(first, last)))
boost::generate(numbers, rand); // ♪
Yanıtlar:
İlk versiyon
std::generate(numbers.begin(), numbers.end(), rand);
bize bir dizi değer oluşturmak istediğinizi söyler.
İkinci versiyonda okuyucunun bunu kendi başına çözmesi gerekecektir.
Yazarken tasarruf etmek genellikle yetersizdir, çünkü çoğunlukla okuma süresinde kaybolur. Çoğu kod yazıldığından çok daha fazla okunur.
numberssebepsiz yere iki kez belirtmeniz gerekir . Dolayısıyla: boost::range::generate(numbers, rand);. İyi oluşturulmuş bir kitaplıkta hem daha kısa hem de daha okunaklı koda sahip olmamanız için hiçbir neden yok.
std::generate(number.begin(), numbers.begin()+3, rand), değil mi? Bu yüzden numberbazen iki kez belirtmek faydalı olabilir sanırım .
std::generate()varsa, bunun yerine , aralığın bir kısmının esnek spesifikasyonuna izin verirken tekrarını kaldıran std::generate(slice(number.begin(), 3), rand)varsayımsal bir aralık dilimleme sözdizimi ile yapabilir veya hatta daha iyi yapabilirsiniz . Üç tartışmadan başlayarak tersini yapmak daha sıkıcıdır. std::generate(number[0:3], rand)numberstd::generate()
For döngüsünün aralık temelli olup olmaması hiç fark etmez, yalnızca parantez içindeki kodu basitleştirir. Algoritmalar, amacı gösterdikleri için daha nettir .
Şahsen benim ilk okumam:
std::generate(numbers.begin(), numbers.end(), rand);
"Bir aralıktaki her şeye atıyoruz. Aralık. numbersAtanan değerler rastgele" dir.
İlk okumam:
for (int& x : numbers) x = rand();
"Bir aralıktaki her şeye bir şeyler yapıyoruz. Aralık. numbersYaptığımız şey rastgele bir değer atamaktır."
Bunlar oldukça benzer, ancak aynı değil. İlk okumayı kışkırtmak isteyebilmemin makul bir nedeni, bence bu kodla ilgili en önemli gerçeğin aralığa ataması olduğunu düşünüyorum. İşte "neden isteyeyim ki ..." Kullanıyorum generateçünkü C ++ ' std::generateda "aralık ataması" anlamına geliyor. Btw'nin yaptığı gibi std::copy, ikisi arasındaki fark atadığınız şeydir.
Yine de kafa karıştırıcı faktörler var. Aralık tabanlı döngülerin numbers, yineleyici tabanlı algoritmalardan daha doğal olarak, aralığın olduğunu ifade etmenin daha doğrudan bir yolu vardır . Bu yüzden insanlar menzile dayalı algoritma kitaplıkları üzerinde çalışıyor: sürümden boost::range::generate(numbers, rand);daha iyi görünüyor std::generate.
Buna karşı, int&menzile dayalı for döngüsünüzde bir kırışıklık var. Ya aralığın değer türü değilse int, o zaman burada dönüştürülebilir olmasına bağlı sinir bozucu derecede ince bir şey yapıyoruz int&, oysa generatekod yalnızca randöğeye atanabilir olmanın getirisine bağlı . Değer türü olsa bile int, hala olup olmadığını düşünmeyi bırakabilirim. Bu nedenle auto, ben neyin atandığını görene kadar türleri düşünmeyi erteliyor - auto &x"ne tür olursa olsun, aralık öğesine bir referans alın" diyorum. (Onlar olduğunuz işlevi şablonları için) idi Geri C ++ 03, algoritmalar kesin türlerini gizlemek için bir yol, şimdi onlar konum bir yol.
En basit algoritmaların eşdeğer döngülere göre yalnızca marjinal bir faydası olduğunu düşünüyorum. Aralık tabanlı döngü döngüleri, döngüleri iyileştirir (bundan biraz daha fazlası olmasına rağmen, temel olarak standart şablonun çoğunu kaldırarak). Dolayısıyla, kenar boşlukları daha sıkı çekilir ve belki bazı özel durumlarda fikrinizi değiştirirsiniz. Ama orada hala bir stil farkı var.
operator int&()? :)
int&ile SomeClass&ve şimdi değil işaretlenmiş dönüşüm operatörleri ve tek parametreli yapıcıları hakkında endişe zorunda explicit.
operator int&()ve operator int const &() constsonra tekrar operator int() constve aşırı yükleyerek çalışabilir operator=(int).
Kanımca, Etkili STL Öğesi 43: "Algoritma çağrılarını elle yazılmış döngülere tercih et." hala iyi bir tavsiye.
begin()/ end()Cehennemden kurtulmak için genellikle sarmalayıcı işlevleri yazıyorum . Bunu yaparsanız, örneğiniz şöyle görünecektir:
my_util::generate(numbers, rand);
Hem amacı iletme hem de okunabilirlik açısından aralığa dayalı döngüyü geçtiğine inanıyorum .
Bunu söyledikten sonra, C ++ 98'de bazı STL algoritma çağrılarının anlatılamaz kodlar verdiğini ve "El ile yazılmış döngüler için algoritma çağrılarını tercih et" ifadesini izlemenin iyi bir fikir olmadığını kabul etmeliyim. Şans eseri, lambdalar bunu değiştirdi.
Herb Sutter'dan şu örneği düşünün : Lambdas, Lambdas Everywhere .
Görev: olduğu v ilk öğesini bulun > xve < y.
Lambdas olmadan:
auto i = find_if( v.begin(), v.end(),
bind( logical_and<bool>(),
bind(greater<int>(), _1, x),
bind(less<int>(), _1, y) ) );
Lambda ile
auto i=find_if( v.begin(), v.end(), [=](int i) { return i > x && i < y; } );
In my ayrıntı düşürebilir olsa görüşüne, manuel döngü, readabitly yoksun:
for (int& x : numbers) x = rand();
Bu döngüyü, sayılarla tanımlanan aralığı 1 başlatmak için kullanmazdım , çünkü ona baktığımda, bir sayı aralığı üzerinde yineliyor gibi görünüyor , ama gerçekte değil (özünde), yani yerine aralıktan okuyor , aralığa yazıyor .
Amaç, kullandığınızda çok daha nettir std::generate.
1. Bu bağlamda başlatma , kabın elemanlarına anlamlı bir değer vermek anlamına gelir .
std::generate, ki bu bir C ++ programcısı olarak kabul edilebilir (eğer aşina değillerse, bakacaklar, aynı sonuç).
&karakteri unutursanız ?) Algoritmaların avantajı, niyet göstermeleridir, döngülerle bunu anlamanız gerekir. Döngünün uygulanmasında bir hata varsa, bunun bir hata mı yoksa kasıtlı mı olduğu açık değildir.
std::generatesadece bir bakışla, bu işlev tarafından bir şey üretildiği söylenebilir ; Ne bir şeylerin işlevine üçüncü argüman tarafından cevaplanır. Bunun çok daha iyi olduğunu düşünüyorum.
Yineleyicileri girdi olarak alan algoritmaların, aralık tabanlı döngülerle yapamayacağınız (basitçe) bazı şeyler vardır. Örneğin std::generate:
Kapsayıcıyı en fazla limit(hariç tutulan, limitgeçerli bir yineleyici açık numbers) bir dağıtımdaki değişkenlerle ve geri kalanını başka bir dağıtımdaki değişkenlerle doldurun .
std::generate(numbers.begin(), limit, rand1);
std::generate(limit, numbers.end(), rand2);
Yineleyici tabanlı algoritmalar, üzerinde çalıştığınız aralık üzerinde size daha iyi bir kontrol sağlar.
Belirli bir durum için std::generate, okunabilirlik / niyet sorunu ile ilgili önceki cevaplara katılıyorum. std :: generate daha açık bir versiyon gibi görünüyor. Ama bunun bir bakıma zevk meselesi olduğunu kabul ediyorum.
Bununla birlikte, std :: algoritmasını çöpe atmamak için başka bir nedenim var - bazı veri türleri için özelleşmiş belirli algoritmalar var.
En basit örnek olacaktır std::fill. Genel sürüm, sağlanan aralık üzerinde bir döngü olarak uygulanır ve bu sürüm, şablonun başlatılması sırasında kullanılacaktır. Ama her zaman değil. Örneğin, ona bir aralık sağlarsanız std::vector<int>- çoğu zaman aslında memsetkaputun altında arayacak ve çok daha hızlı ve daha iyi bir kod üretecektir .
Bu yüzden burada bir verimlilik kartı oynamaya çalışıyorum.
El yazısı döngünüz std :: algoritma sürümü kadar hızlı olabilir, ancak daha hızlı olamaz. Dahası, std :: algoritması belirli kaplar ve türler için özelleştirilebilir ve temiz STL arabirimi altında yapılır.
Cevabım belki olur ve hayır. C ++ 11'den bahsediyorsak, o zaman belki (daha çok hayır gibi). Örneğin std::for_each, lambdalarda bile kullanmak gerçekten can sıkıcıdır:
std::for_each(c.begin(), c.end(), [&](ExactTypeOfContainedValue& x)
{
// do stuff with x
});
Ancak aralık tabanlı kullanmak çok daha iyidir:
for (auto& x : c)
{
// do stuff with x
}
Öte yandan, C ++ 1y'den bahsediyorsak, o zaman hayır, algoritmalar için menzile dayalı olarak kullanılmaz hale gelmeyeceğini iddia ediyorum. C ++ standart komitesinde, C ++ 'ya aralıklar eklemek için bir öneri üzerinde çalışan bir çalışma grubu vardır ve ayrıca polimorfik lambdalar üzerinde de çalışmalar yapılmaktadır. Aralıklar yineleyici çifti kullanma ihtiyacını ortadan kaldırır ve polimorfik lambda, lambda'nın tam bağımsız değişken türünü belirtmenize izin vermez. Bu, bunun std::for_eachşu şekilde kullanılabileceği anlamına gelir (bunu zor bir gerçek olarak almayın, bugün rüyalar tam olarak nasıl görünür)
std::for_each(c.range(), [](x)
{
// do stuff with x
});
[]lambda ile yazarak sıfır yakalama belirlemenizdir. Yani, sadece bir döngü gövdesi yazmakla karşılaştırıldığında, sözcüksel olarak göründüğü değişken arama bağlamından bir kod yığınını izole ettiniz. İzolasyon genellikle okuyucuya yardımcı olur, okurken düşünmesi daha azdır.
for_eachbir lambda ile kullanıldığında bile hala anlamsız. foreach + yakalama lambda şu anda aralık tabanlı bir for döngüsü yazmanın ayrıntılı bir yoludur ve biraz daha az ayrıntılı bir yol haline gelir, ancak yine de döngüden daha fazladır. for_eachElbette savunmanız gerektiğini düşündüğümden değil , ama cevabınızı görmeden önce bile soruyu soran kişi algoritmaları yenmek isterse, for_eacholası tüm hedefler arasında en yumuşak olanı seçebilirdi diye düşünüyordum ;-)
for_each, ancak menzil tabanlı için küçük bir avantajı var - dönüştürmek için paralel_ ile ön ekleyerek daha kolay paralel hale getirebilirsiniz parallel_for_each(PPL kullanıyorsanız ve bunu yapmanın iş parçacığı güvenli olduğunu varsayarak) . :-D
std::algorithm.
Dikkat edilmesi gereken bir şey, bir algoritmanın nasıl yapıldığını değil, ne yapıldığını ifade etmesidir.
Menzile dayalı döngü, işlerin yapılma şeklini içerir: ilk ile başlayın, uygulayın ve sonuna kadar bir sonraki öğeye gidin. Basit bir algoritma bile işleri farklı şekilde yapabilir (en azından belirli kapsayıcılar için bazı aşırı yüklemeler, korkunç vektörü düşünmeden bile) ve en azından yapılma şekli yazar işi değildir.
Bence bu farkın büyük bir kısmı, olabildiğince özetleyin ve bu, yapabildiğiniz zaman, algoritmaları kullanın.
Menzile dayalı for-döngüsü tam da budur. Tabii ki standart değişene kadar.
Algoritma bir işlevdir. Parametrelerine bazı gereksinimler getiren bir işlev. Gereksinimler, mevcut tüm yürütme iş parçacıklarından yararlanan ve sizi otomatik olarak hızlandıracak örnek uygulamaya izin vermek için bir standartta ifade edilmiştir.