== ve! = Karşılıklı bağımlı mıdır?


292

Ben C ++ operatör aşırı yükleme hakkında öğreniyorum ve bunu görüyorum ==ve !=sadece kullanıcı tanımlı türleri için özelleştirilebilir bazı özel fonksiyonlar vardır. Benim endişem, neden iki ayrı tanım gerekli? Ben eğer düşünülmektedir a == bdoğrudur, o zaman a != b, tanımı gereği, çünkü tersi otomatik sahte ve yardımcısı olduğunu ve başka hiçbir olasılığı vardır a != bolduğunu !(a == b). Bunun doğru olmadığı bir durum hayal edemedim. Ama belki de hayal gücüm sınırlı ya da bir şeyden habersizim?

Birini diğeri açısından tanımlayabileceğimi biliyorum, ama istediğim bu değil. Nesneleri değere veya kimliğe göre karşılaştırma arasındaki farkı da sormuyorum. Ya da iki nesnenin aynı anda eşit ve eşit olup olmayacağı (bu kesinlikle bir seçenek değildir! Bu şeyler birbirini dışlar). Sorduğum şey şu:

İki nesne hakkında eşit olmakla ilgili soru sormanın anlamlı olduğu, ancak eşit olmamalarını sormanın anlamlı olmadığı herhangi bir durum var mı ? (kullanıcının bakış açısından veya uygulayıcının bakış açısından)

Böyle bir olasılık yoksa, neden C ++ 'da bu iki işleç iki farklı işlev olarak tanımlanıyor?


13
İki işaretçi boş olabilir, ancak her zaman eşit olmayabilir.
Ali Çağlayan

2
Burada mantıklı olup olmadığından emin değilim, ama bunu okumak 'kısa devre' meselelerini düşündürdü. Örneğin, 'undefined' != expressionifadenin değerlendirilip değerlendirilemeyeceğine bakılmaksızın, her zaman doğru (veya yanlış veya tanımsız) tanımlanabilir. Bu durumda a!=btanım gereği doğru sonucu döndürür, ancak değerlendirilemezse !(a==b)başarısız olur b. (Veya değerlendirme pahalıysa çok zaman ayırın b).
Dennis Jaheruddin

2
Null! = Null ve null == null ne olacak? Her ikisi de olabilir ... yani a! = B her zaman a == b anlamına gelmezse.
zozo

4
Javascript(NaN != NaN) == true
chiliNUT

Yanıtlar:


272

Sen olur değil dili otomatik olarak yeniden yazmak istiyorum a != bolarak !(a == b)ne zaman a == bbir başka döner bir şey bool. Ve bunu yapabilmenizin birkaç nedeni var.

a == bHerhangi bir karşılaştırma yapmak istemeyen ve yapmayan ifade oluşturucu nesneleriniz olabilir , ancak sadece temsil eden bir ifade düğümü oluşturur a == b.

a == bDoğrudan bir karşılaştırma yapmak istemeyen ve yapmayan tembel bir değerlendirmeniz olabilir , ancak bunun yerine karşılaştırmayı gerçekten gerçekleştirmek için daha sonraki bir zamanda örtülü veya açık bir lazy<bool>şekilde dönüştürülebilen bir tür döndürür bool. Değerlendirmeden önce tam ifade optimizasyonu sağlamak için ifade oluşturucu nesneleriyle birleştirilebilir.

optional<T>İsteğe bağlı değişkenler verildiğinde tve uizin vermek istediğiniz t == u, ancak döndürmesini istediğiniz bazı özel şablon sınıflarınız olabilir optional<bool>.

Muhtemelen aklıma gelmeyen başka şeyler de var. Ve bu örneklerde operasyon a == bve a != bher ikisi de anlamlı olsa da, yine a != bde aynı şey değildir !(a == b), bu yüzden ayrı tanımlara ihtiyaç vardır.


72
İfade oluşturma, bunun ne zaman istendiğine dair, fethedilmiş senaryolara dayanmayan harika bir pratik örnektir.
Oliver Charlesworth

6
Başka bir iyi örnek, vektör mantıksal işlemleri olacaktır. O zaman !=iki geçişli bilgi işlem yerine veri bilgi işleminden bir geçiş yapmak istersiniz . Özellikle döngüleri birleştirmek için derleyiciye güvenemediğiniz günlerde. Ya da bugün bile derleyiciyi ikna edemiyorsanız, vektörleriniz üst üste gelmez. ==!

41
"Sen nesneler oluşturucu ifadeyi olabilir" - iyi o zaman operatör !de bazı ifade düğümü inşa edebilirsiniz ve biz hala ince değiştiriyorsanız a != bile !(a == b)bugüne kadar bu gider. Aynı şey lazy<bool>::operator!geri dönebilir lazy<bool>. optional<bool>daha mantıklıdır, çünkü örneğin mantıksal doğruluğu boost::optional, değerin kendisine değil bir değerin var olup olmadığına bağlıdır.
Steve Jessop

42
Bütün bunlar ve Nans - lütfen NaNs'yi hatırlayın ;
jsbueno

9
@jsbueno: NaN'lerin bu bakımdan özel olmadığı belirtildi.
Oliver Charlesworth

110

Böyle bir olasılık yoksa, neden C ++ 'da bu iki işleç iki farklı işlev olarak tanımlanıyor?

Onları aşırı yükleyebileceğiniz ve onları aşırı yükleyerek onlara orijinallerinden tamamen farklı bir anlam verebilirsiniz.

Örneğin, <<şu anda bir yerleştirme operatörü olarak aşırı yüklenmiş olan aslen bitsel sol kaydırma operatörünü ele alalım std::cout << something; orijinal olandan tamamen farklı bir anlam.

Bu nedenle, aşırı yüklediğinizde bir operatörün anlamının değiştiğini kabul ederseniz, kullanıcının operatöre operatöre ==tam olarak operatörün olumsuzlaması olmayan bir anlam vermesini önlemek için hiçbir neden yoktur !=, ancak bu kafa karıştırıcı olabilir.


18
Pratik mantıklı olan tek cevap budur.
Sonic Atom

2
Bana öyle geliyor ki, neden ve sonuç geriye dönük. Bunları ayrı olarak aşırı yükleyebilirsiniz ==ve !=farklı operatörler olarak var olabilirsiniz. Öte yandan, muhtemelen ayrı operatörler olarak mevcut değiller, çünkü bunları ayrı olarak aşırı yükleyebilirsiniz, ancak eski ve rahatlık (kod kısalığı) nedeniyle.
nitro2k01

60

Benim endişem, neden iki ayrı tanım gerekli?

Her ikisini de tanımlamanız gerekmez.
Eğer birbirini dışlayanlarsa, sadece std :: rel_ops tanımlayarak ==ve <yanında özlü olabilirsiniz.

Fom cpreference:

#include <iostream>
#include <utility>

struct Foo {
    int n;
};

bool operator==(const Foo& lhs, const Foo& rhs)
{
    return lhs.n == rhs.n;
}

bool operator<(const Foo& lhs, const Foo& rhs)
{
    return lhs.n < rhs.n;
}

int main()
{
    Foo f1 = {1};
    Foo f2 = {2};
    using namespace std::rel_ops;

    //all work as you would expect
    std::cout << "not equal:     : " << (f1 != f2) << '\n';
    std::cout << "greater:       : " << (f1 > f2) << '\n';
    std::cout << "less equal:    : " << (f1 <= f2) << '\n';
    std::cout << "greater equal: : " << (f1 >= f2) << '\n';
}

İki nesne hakkında eşit olmakla ilgili soru sormanın anlamlı olduğu, ancak eşit olmamalarını sormanın anlamlı olmadığı herhangi bir durum var mı?

Bu operatörleri sıklıkla eşitlikle ilişkilendiririz.
Temel türler üzerinde bu şekilde davranmalarına rağmen, bunun özel veri türleri üzerindeki davranışları olma zorunluluğu yoktur. İstemezsen bir bool bile geri vermek zorunda değilsin.

İnsanların operatörleri tuhaf yollarla aşırı yüklediklerini gördüm, sadece alanlarına özgü uygulamaları için mantıklı olduğunu bulmak için. Arayüz, birbirini dışlayan olduklarını gösteriyor gibi görünse bile, yazar belirli bir iç mantık eklemek isteyebilir.

(kullanıcının bakış açısından veya uygulayıcının bakış açısından)

Belirli bir örnek istediğinizi biliyorum,
bu yüzden pratik olduğunu düşündüğüm Catch test çerçevesinden biri :

template<typename RhsT>
ResultBuilder& operator == ( RhsT const& rhs ) {
    return captureExpression<Internal::IsEqualTo>( rhs );
}

template<typename RhsT>
ResultBuilder& operator != ( RhsT const& rhs ) {
    return captureExpression<Internal::IsNotEqualTo>( rhs );
}

Bu operatörler farklı şeyler yapıyorlar ve bir yöntemi diğerinin! (Değil) olarak tanımlamak mantıklı olmazdı. Bunun yapılmasının nedeni, çerçevenin yapılan karşılaştırmayı yazdırabilmesidir. Bunu yapmak için aşırı yüklenmiş operatörün kullanıldığı bağlamı yakalaması gerekir.


14
Aman, nasıl olabilir değil biliyorum std::rel_ops? Bunu işaret ettiğiniz için çok teşekkür ederim.
Daniel Jour

5
Cppreference'den (veya başka bir yerden) neredeyse kelimelere yakın kopyalar açıkça işaretlenmeli ve uygun şekilde ilişkilendirilmelidir. rel_opsyine de korkunç.
TC

@TC Kabul ediyorum, sadece OP'nin alabileceği bir yöntem olduğunu söylüyorum. Rel_ops gösterilen örnek daha basit açıklamak nasıl bilmiyorum. Ben nerede olduğunu bağlantılı, ancak referans sayfası her zaman değişebilir beri kod yayınladı.
Trevor Hickey

4
Kod örneğinin kendinizin değil, cppreference'den% 99 olduğunu açıkça belirtmeniz gerekir.
TC

2
Std :: relops iyilikten düşmüş gibi görünüyor. Daha hedeflenmiş bir şey için boost ops'a göz atın.
JDługosz

43

Orada bazı çok köklü kurallar vardır (a == b)ve (a != b)şunlardır hem yalancı mutlaka karşıtların değil. Özellikle, SQL'de, NULL ile yapılan herhangi bir karşılaştırma doğru veya yanlış değil NULL değerini verir.

Mümkünse bunun için yeni örnekler oluşturmak muhtemelen iyi bir fikir değildir, çünkü bu çok sezgisel değildir, ancak mevcut bir konvansiyonu modellemeye çalışıyorsanız, operatörlerinizin bunun için "doğru" davranmalarını sağlama seçeneğine sahip olmak güzel bağlamı.


4
C ++ 'da SQL benzeri null davranış uygulamak? Ewwww. Ama sanırım bu dilde yasaklanması gerektiğini düşündüğüm bir şey değil, ama tatsız da olabilir.

1
@ dan1111 Daha da önemlisi, SQL'in bazı lezzetleri c ++ 'da iyi kodlanmış olabilir, bu nedenle dilin sözdizimini desteklemesi gerekir, değil mi?
Joe

1
Yanlışsam beni düzeltin, burada wikipedia'dan çıkıyorum , ancak SQL'de NULL değeriyle karşılaştırma Bilinmiyor, Yanlış değil mi? Ve Bilinmeyen'in olumsuzlaması hala Bilinmiyor mu? SQL mantığı C ++ ile kodlanmışsa, NULL == somethingBilinmeyen'i döndürmek istemezsiniz ve ayrıca NULL != somethingBilinmeyen'i de döndürmek istersiniz !Unknownve geri dönmek istersiniz Unknown. Ve bu durumda operator!=, olumsuzlama olarak operator==uygulamak hala doğrudur.
Benjamin Lindley

1
@Barmar: Tamam, ama sonra bu "SQL NULLs bu şekilde çalışır" deyimini nasıl doğru hale getirir ? Karşılaştırma operatörü uygulamalarımızı geri dönen booleanlarla kısıtlıyorsak, bu sadece bu operatörlerle SQL mantığı uygulamanın imkansız olduğu anlamına gelmez mi?
Benjamin Lindley

2
@Barmar: Hayır, mesele bu değil. OP zaten bu gerçeği biliyor ya da bu soru olmayacaktı. Nokta 1 ya mantıklı bir örnek) birini uygulamak sunmaktır operator==ya operator!=uygulamak, fakat başka, ya da 2) operator!=olumsuzlaması başka bir şekilde operator==. Ve NULL değerleri için SQL mantığı uygulamak bunun bir örneği değildir.
Benjamin Lindley

23

Sorunuzun yalnızca ikinci kısmını cevaplayacağım, yani:

Böyle bir olasılık yoksa, neden C ++ 'da bu iki işleç iki farklı işlev olarak tanımlanıyor?

Geliştiricinin her ikisini de aşırı yüklemesine izin vermenin mantıklı olmasının bir nedeni performanstır. Hem ==ve öğelerini uygulayarak optimizasyonlara izin verebilirsiniz !=. O x != yzaman daha ucuz olabilir!(x == y) . Bazı derleyiciler bunu sizin için optimize edebilir, ancak belki de, özellikle çok fazla dallanma içeren karmaşık nesneleriniz varsa.

Geliştiricilerin yasaları ve matematiksel kavramları çok ciddiye aldığı Haskell'de bile, her ikisinin de aşırı yüklenmesine izin verilir ==ve /=burada da görebileceğiniz gibi ( http://hackage.haskell.org/package/base-4.9.0.0/docs/Prelude .html # v: -61--61- ):

$ ghci
GHCi, version 7.10.2: http://www.haskell.org/ghc/  :? for help
λ> :i Eq
class Eq a where
  (==) :: a -> a -> Bool
  (/=) :: a -> a -> Bool
        -- Defined in `GHC.Classes'

Bu muhtemelen mikro optimizasyon olarak kabul edilir, ancak bazı durumlarda garanti edilebilir.


3
SSE (x86 SIMD) sarmalayıcı sınıfları bunun harika bir örneğidir. Bir pcmpeqbkomut var, ama! = Maskesi üreten paketlenmiş karşılaştırma talimatı yok. Dolayısıyla, sonuçları kullanan şeyin mantığını tersine çeviremiyorsanız, tersine çevirmek için başka bir talimat kullanmanız gerekir. (Eğlenceli gerçek: AMD'nin XOP talimat setinin paketlenmiş karşılaştırması var neq. Çok kötü Intel XOP'yi benimsemedi / uzatmadı; yakında ölecek olan ISA uzantısında bazı yararlı talimatlar var.)
Peter Cordes

1
İlk etapta SIMD'nin tüm noktası performanstır ve genellikle sadece genel perf için önemli olan döngülerde manuel olarak kullanmak için uğraşırsınız. PXORSıkı bir döngüde tek bir talimatı ( karşılaştırma maskesi sonucunu tersine çevirmek için hepsi ile) kaydetmek önemli olabilir.
Peter Cordes

Genel giderlerden biri mantıklı bir olumsuzlama olduğunda, bir sebep olarak performans güvenilir değildir .
Şerefe ve s. - Alf

Hesaplamanın x == ymaliyeti çok daha önemliyse, birden fazla mantıksal olumsuzlama olabilir x != y. İkincisini hesaplamak, şube tahmini
vb.Nedeniyle

16

İki nesne hakkında eşit olmakla ilgili soru sormanın anlamlı olduğu, ancak eşit olmamalarını sormanın anlamlı olmadığı herhangi bir durum var mı? (kullanıcının bakış açısından veya uygulayıcının bakış açısından)

Bu bir görüş. Belki de yoktur. Ancak her şeyi bilen olmayan dil tasarımcıları, mantıklı gelebilecek durumlarla karşılaşabilecek insanları (en azından onlar için) kısıtlamamaya karar verdiler.


13

Düzenlemeye yanıt olarak;

Yani, eğer bir tipin operatöre sahip olması mümkün ==ise !=, ya da tam tersi mümkün değilse ve ne zaman mantıklıdır?

In genel , hayır, mantıklı değil. Eşitlik ve ilişkisel operatörler genellikle kümeler halinde gelir. Eşitlik varsa, o zaman eşitsizlik; daha az, sonra daha büyük ve benzeri<= vb Benzer bir yaklaşım, hem de aritmetik operatör uygulanır, aynı zamanda, genel olarak doğal mantıksal gruplar halindedir.

Bu, std::rel_ops ad alanında . Eşitliği uygularsanız ve işleçlerden daha azını uygularsanız, bu ad alanını kullanmak, diğer uygulanmış işleçleriniz açısından uygulanan diğerlerini verir.

Hepsinin söylediği gibi, birinin diğeri anlamına gelmeyeceği veya diğerleri açısından uygulanamayacağı koşullar veya durumlar var mı? Evet var , tartışmasız çok az, ama oradalar; yine,rel_ops kendi ad alanı . Bu nedenle, bağımsız olarak uygulanmasına izin vermek, kodun kullanıcısı veya istemcisi için hala doğal ve sezgisel bir şekilde ihtiyacınız olan veya ihtiyacınız olan semantiği elde etmek için dili kullanmanıza olanak tanır.

Daha önce bahsedilen tembel değerlendirme bunun mükemmel bir örneğidir. Bir başka iyi örnek de onlara eşitlik veya eşitsizlik anlamına gelmeyen anlambilim vermektir. Buna benzer bir örnek, bit kaydırma operatörleri <<ve >>akım ekleme ve çıkarma için kullanılmasıdır. Genel çevrelerde kaşlarını çatmasına rağmen, bazı alana özgü alanlarda mantıklı olabilir.


12

Ve ==ve !=operatörleri aslında eşitlik anlamına gelmezse, ve <<ve >>operatörleri de bit kaydırma anlamına gelmez. Sembollere başka bir kavram anlamına geliyorlarsa, birbirlerini dışlamaları gerekmez.

Eşitlik açısından, kullanım durumunuzun nesnelere karşılaştırılamaz olarak muamele etmesini gerektirmesi mantıklı olabilir, böylece her karşılaştırma yanlış (veya işleçleriniz bool olmayan döndürürse karşılaştırılamaz bir sonuç türü) döndürmelidir. Bunun gerekli olacağı belirli bir durumu düşünemiyorum, ama bunun yeterince makul olduğunu görebiliyordum.


7

Büyük güç ile sorumlu büyük gelir, ya da en azından gerçekten iyi stil rehberleri.

==ve !=ne istersen yapmak için aşırı yüklenebilir. Hem bir nimet hem de bir lanet. Hiçbir garantisi yoktur !=araçlar !(a==b).


6
enum BoolPlus {
    kFalse = 0,
    kTrue = 1,
    kFileNotFound = -1
}

BoolPlus operator==(File& other);
BoolPlus operator!=(File& other);

Bu operatörün aşırı yüklenmesini haklı gösteremiyorum, ancak yukarıdaki örnekte operator!="karşıt" olarak tanımlamak imkansız operator==.



1
@Karman: Dafang, bunun iyi bir numaralandırma (ne de böyle bir numaralandırmayı tanımlamak için iyi bir fikir) olduğunu söylemez, sadece bir noktayı göstermek için bir örnektir. Bu (belki de kötü) operatör tanımıyla, !=bunun tam tersi anlamına gelmez ==.
AlainD

1
Gönderdiğim bağlantıyı tıkladınız ve bu sitenin amacının farkında mısınız? Buna "mizah" denir.

1
@Snowman: Kesinlikle yaparım ... üzgünüm, bir bağlantı olduğunu özledim ve ironi olarak tasarladım! : o)
AlainD

Bekle, aşırı yüklüyorsun ==?
LF

5

Sonunda, bu işleçlerle denetlediğiniz şey, ifadenin a == bveya a != bbir Boole değeri ( trueveya false) döndürmesidir . Bu ifade, birbirini dışlayan olmak yerine karşılaştırmadan sonra bir Boole değeri döndürür.


4

[..] neden iki ayrı tanım gerekli?

Dikkate alınması gereken bir şey, bu operatörlerden birini diğerinin olumsuzlamasını kullanmaktan daha verimli bir şekilde uygulama olasılığı olabilir.

(My örnek burada çöp oldu, ama hala duruyor noktası, örneğin, çiçeklenme filtrelerin düşünüyorum: Onlar hızlı bir şeyse test izin değil . Bir sette, ancak çok daha fazla zaman alabilir içinde ise test)

[..] tanımı gereği, a != bbir !(a == b).

Ve bunu bekletmek programcı olarak sizin sorumluluğunuzdadır. Muhtemelen bir test yazmak için iyi bir şey.


4
!((a == rhs.a) && (b == rhs.b))Kısa devreye nasıl izin vermez? eğer !(a == rhs.a), o (b == rhs.b)zaman değerlendirilmez.
Benjamin Lindley

Yine de bu kötü bir örnek. Kısa devre burada büyülü bir avantaj sağlamaz.
Oliver Charlesworth

@Oliver Charlesworth Yalnız değil, ancak ayrı operatörlerle birleştiğinde şunları yapar: Durumunda ==, ilk karşılık gelen elemanlar eşit olmaz olmaz karşılaştırmayı durduracaktır. Ancak !=, eğer açısından uygulanmışsa, ==eşit olmadıklarını söyleyebilmek için önce ilgili tüm unsurları (hepsi eşit olduğunda) karşılaştırmak gerekir: P Ama Yukarıdaki örnekte, eşit olmayan ilk çifti bulur bulmaz karşılaştırmayı durduracaktır. Gerçekten harika bir örnek.
BarbaraKwarc

@BenjaminLindley Doğru, örneğim tam bir saçmalıktı. Ne yazık ki, başka bir atm bulamıyorum, burada çok geç.
Daniel Jour

1
@BarbaraKwarc: !((a == b) && (c == d))ve (a != b) || (c != d)kısa devre verimliliği açısından eşdeğerdir.
Oliver Charlesworth

2

Operatörlerin davranışlarını özelleştirerek, istediğiniz şeyi yapmalarını sağlayabilirsiniz.

Bir şeyleri özelleştirmek isteyebilirsiniz. Örneğin, bir sınıfı özelleştirmek isteyebilirsiniz. Bu sınıftaki nesneler yalnızca belirli bir özelliği kontrol ederek karşılaştırılabilir. Durumun böyle olduğunu bilerek, tüm nesnenin her bir özelliğinin her bir bitini kontrol etmek yerine, yalnızca minimum şeyleri kontrol eden belirli bir kod yazabilirsiniz.

Bir şeyin aynı olduğunu bulabileceğinizden daha hızlı olmasa da, daha hızlı olmasa da farklı olduğunu anlayabileceğiniz bir durum düşünün. Bir şeyin aynı mı yoksa farklı mı olduğunu anladıktan sonra, tam tersini biraz çevirerek anlayabilirsiniz. Ancak, bu biti çevirmek fazladan bir işlemdir. Bazı durumlarda, kod çok fazla yeniden yürütüldüğünde, bir işlemin kaydedilmesi (birçok kez çoğaltılır) genel bir hız artışı sağlayabilir. (Örneğin, bir megapiksel ekranın piksel başına bir işlem kaydederseniz, sadece bir milyon işlem kaydettiniz. Saniyede 60 ekranla çarpılır ve daha da fazla işlem kaydedersiniz.)

hvd'nin cevabı bazı ek örnekler sağlar.


2

Evet, çünkü biri "eşdeğer" ve diğeri "eşdeğer olmayan" anlamına gelir ve bu terimler birbirini dışlar. Bu operatörler için başka herhangi bir anlam kafa karıştırıcıdır ve kesinlikle kaçınılmalıdır.


Tüm durumlar için karşılıklı olarak münhasır değildirler . Örneğin, iki sonsuzluk hem birbirine eşit değildir hem de birbirine eşit değildir.
vladon

@vladon genel durumda birini yerine diğerini kullanabilir miyim? Hayır. Bu eşit olmadığı anlamına geliyor. Geri kalan her şey operatör yerine özel bir işleve gider == /! =
oliora

@vladon lütfen, genel durum yerine cevabımdaki tüm vakaları okuyun .
oliora

@vladon Matematikte bu kadar doğru olduğu yerde, a != b!(a == b) için, C'de bu nedenle eşit olmayan ?
nitro2k01

2

Belki karşılaştırılamaz bir kural. a != b oldu sahte ve a == boldu yanlış bir vatansız biraz severim.

if( !(a == b || a != b) ){
    // Stateless
}

Eğer mantıksal sembolleri yeniden düzenlemek istiyorsanız! ([A] || [B]) mantıksal olarak ([! A] & [! B]) olur
Thijser

Dönüş türünün operator==()ve operator!=()zorunlu olarak olması gerekmediğine dikkat edin bool, isterseniz vatansız içeren bir numaralandırma olabilir ve yine de operatörler hala tanımlanmış olabilir (a != b) == !(a==b).
lorro
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.