Mantıksal NOT işleci neden C tarzı dillerde “!” Değil “~~” değil?


39

İkili operatörler için hem bitsel hem de mantıksal operatörlere sahibiz:

& bitwise AND
| bitwise OR

&& logical AND
|| logical OR

NOT (unary operatörü) yine de farklı davranıyor. Bitsel ~ için var ve! mantıklı.

NOT'un VE ve VEYA'nın aksine tek bir işlem olduğunu kabul ediyorum, ancak tasarımcıların burada bekarların bitsel ve çiftlerin mantıklı olması ve bunun yerine farklı bir karaktere sahip olma prensibinden sapmayı seçmelerinin bir nedeni olduğunu düşünemiyorum. Sanırım yanlış okuyabilirsin, her zaman operand değerini döndürecek çift yönlü bir işlem gibi. Ama bu bana gerçek bir sorun gibi görünmüyor.

Kaçırmamın bir nedeni var mı?


7
Çünkü eğer !! mantıklı değil, 42'yi 1'e nasıl çevirebilirim? :)
candied_orange

9
Misiniz ~~Mantıksal operatör bitsel operatör ikiye katlanması olduğunu biçim kullanılıyorsa, sonra DEĞİL mantıksal için daha tutarlı olamazdı?
Bart van Ingen Schenau

9
İlk olarak, eğer tutarlılık için olsaydı, kısa devre ile ikiye katlanması ve / veya ilişkili olması; ve mantıksal kısa devre yok.
Christophe

3
Tipik kullanım durumlarında altta yatan tasarım sebebinin görsel netlik ve ayrım olduğunu düşünüyorum. İkili (iki işlemlikli) işleçler, son eklerdir (ve boşluklarla ayrılma eğilimindedir);
Steve

7
Bazı yorumlar zaten (ve takip etmek istemeyen kişiler için ima gibi bu bağlantıyı , !!foo? Yaygın değildir (a-nadir değildir verilmez) dilidir Bu bir sıfır ya da sıfırdan farklı-argüman normalleştirir. 0Ya 1.
Keith Thompson

Yanıtlar:


108

Garip bir şekilde, C tarzı programlama dilinin geçmişi C ile başlamıyor.

Dennis Ritchie, bu makalede C'nin doğumunun zorluklarını iyi açıklıyor .

Bunu okurken, C'nin dil tasarımının bir kısmını kendinden önceki BCPL'den ve özellikle operatörlerden miras aldığı anlaşılıyor . Yukarıda belirtilen makalenin “Yenidoğan C” BCPL en açıklar &ve |iki yeni operatörle zenginleştirildi &&ve ||. Sebepler şunlardı:

  • birlikte kullanılmasından dolayı farklı öncelikler gerekiyordu. ==
  • farklı değerlendirme mantığı: sol-sağ değerlendirme ile kısa devre (zaman, yani abir falsein a&&b, bdeğerlendirilmez).

İlginçtir ki, bu iki katına çıkma okuyucu için herhangi bir belirsizlik yaratmaz: a && bolarak yanlış yorumlanmayacaktır a(&(&b)). Ayrıştırma açısından bakıldığında, belirsizlik de yoktur: bir değer &bolsaydı anlamlı olabilirdi b, ancak bitsel &bir tamsayı operandına ihtiyaç duyacağı bir gösterge olurdu, bu yüzden mantıklı VE tek makul seçenek olacaktır.

BCPL zaten ~bitsel olumsuzlama için kullanıldı . Dolayısıyla, tutarlılık bakış açısından, ona ~~mantıklı bir anlam vermek için iki katına çıkarılabilirdi . Maalesef bu son derece belirsiz olacaktı çünkü ~tek bir operatör: ~~baynı zamanda demek olabilir ~(~b)). Bu yüzden eksik olumsuzlama için başka bir sembol seçilmek zorunda kaldı.


10
Ayrıştırıcı iki durumu açıklayamıyor, bu nedenle dil tasarımcılarının yapması gerekiyor.
BobDalgleish

16
@ Steve: Gerçekten de, C ve C benzeri dillerde zaten pek çok benzer sorun var. Ne zaman ayrıştırıcı görür (t)+1bir ilave olması (t)ve 1ya bunun bir dökme olduğunu +1türüne t? C ++ tasarımının >>doğru şekilde içeren şablonların nasıl lex haline getirileceği problemini çözmesi gerekiyordu . Ve bunun gibi.
Eric Lippert

6
@ user2357112 Mesele şu ki, belirticinin &&tek bir &&belirteç olarak ve iki &belirteç olarak kör bir şekilde alması sorun değil , çünkü a & (&b)yorum yazmanın makul bir şey olmadığı, bu yüzden bir insan asla böyle bir şey ifade etmeyecek ve şaşıracaktı. derleyici olarak davranıyor a && b. Her ikisi de !(!a)ve !!abir insanın ifade etmesi muhtemel şeyler olsa da , derleyicinin belirsizliği isteğe bağlı bir belirleme düzeyi kuralıyla çözmesi kötü bir fikirdir.
Ben

17
!!yazması sadece mümkün / mantıklı değil, kanonik "boole dönüştür" deyimdir.
R. ..

4
Ben dan04 belirsizlik atıfta düşünüyorum --avs -(-a)geçerli sözdizimsel ancak farklı anlamlara sahip, her ikisi de.
Ruslan

49

Tasarımcıların, bekarların bitsel ve ikiye katlamanın mantıklı olduğu ilkesinden sapmayı seçmelerinin bir nedeni olduğunu düşünemiyorum,

İlk başta ilke bu değil; bir kere anladığınızda, daha mantıklı geliyor.

&Rakipleri düşünmenin daha iyi yolu ikili ve Boolean&& değildir . Daha iyi bir yol onları istekli ve tembel olarak düşünmektir . Operatör sol ve sağ tarafını çalıştırır ve sonra sonucu hesaplar. Operatör sol tarafını çalıştırır ve sonucu hesaplamak için sadece gerektiğinde ardından sağ tarafını yürütür.&&&

Dahası, "ikili" ve "Boole" hakkında düşünmek yerine, gerçekte ne olduğunu düşünün. "İkili" sürüm sadece bir kelime içine paketlenmiş bir Boolean dizisi üzerinde Boole işlemi yapıyor .

Öyleyse bir araya getirelim. Bir dizi Boolean'da tembel bir işlem yapmanın bir anlamı var mı ? Hayır, çünkü önce kontrol edilecek "sol taraf" yok. İlk kontrol etmek için 32 "sol taraf" var. Bu yüzden tembel operasyonları tek bir Boolean ile kısıtlıyoruz ve işte buradakilerden birinin "ikili" ve "Boolean" olduğu yönündeki sezgileriniz , tasarımın değil, tasarımın bir sonucudur !

Ve bu şekilde düşündüğünüzde, neden niçin !!ve niçin olmadığı açıkça ortaya çıkıyor ^^. Bu operatörlerin hiçbiri, operatörlerden birini analiz etmeyi atlayamayacağınız bir özelliğe sahip değildir; "tembel" yoktur notya da yoktur xor.

Diğer diller bunu daha net hale getirir; Bazı diller and"istekli ve" ancak and also"tembel ve" anlamına gelir. Ve diğer diller de bunu daha açık hale getirir &ve &&"ikili" ve "Boolen" değildir; Örneğin, C # 'da, her iki sürüm de Boole'leri operad olarak alabilir.


2
Teşekkür ederim. Bu benim için gerçek göz açıcı. Çok kötü, iki cevabı kabul edemiyorum.
Martin Maat

10
Bunun iyi düşünmek yoludur sanmıyorum &ve &&. Şevk arasındaki farkların biri olsa da &ve &&, &bir istekli sürümünden tamamen farklı davranır &&özellikle dilde, nerede &&özel bir boolean türü dışındaki destekler türleri.
user2357112

14
Örneğin, C ve C ++ ' 1 & 2dan tamamen farklı bir sonuç var 1 && 2.
user2357112

7
@ZizyArcher: Yukarıdaki yorumda da belirttiğim gibi, boolC cinsinden bir yazıyı atma kararının etkileyici etkileri var. İkimiz de ihtiyaç !ve ~bir araç ve bir aracı "tek Boolean olarak int tedavi" çünkü "Booleans bir paketlenmiş dizi olarak bir int muamele". Ayrı bool ve int türleriniz varsa, bence daha iyi bir tasarım olacak olan tek bir operatöre sahip olabilirsiniz, ancak bu konuda neredeyse 50 yıl geçtik. C # bu tasarımı tanıdıklık için koruyor.
Eric Lippert

3
@Steve: Eğer cevap saçma görünüyorsa, o zaman bir yerde zayıf ifade edilmiş bir argüman yaptım ve otoritenin argümanına güvenmemeliyiz. Bu konuda saçma görünen şeyler hakkında daha fazla şey söyleyebilir misin?
Eric Lippert

21

TL; DR

C !ve ~işleçlerini başka bir dilden miras aldı. Hem &&ve ||farklı bir kişi tarafından yıl sonra eklenmiştir.

Uzun cevap

Tarihsel olarak, C, Algol'a dayanan CPL'ye dayanan BCPL'ye dayanan B'nin ilk dillerinden gelişmiştir.

C ++, Java ve C # 'nin büyük büyükbabası Algol , programcıların sezgisel göründüğü bir şekilde doğru ve yanlış olarak tanımlandı: “İkili sayı olarak kabul edilen (1'e karşılık gelen ve 0'a yanlış olan) gerçek değerler içsel integral değeri ile aynı ”. Bununla birlikte, bunun bir dezavantajı, mantıksal ve bitsel olarak aynı işlem olamaz: Herhangi bir modern bilgisayarda, ~01 yerine 1'e ve 1 yerine ~1-2'ye eşittir. (Altmış yıllık bir ana bilgisayarda bile ~0- 0 veya INT_MIN, ~0 != 1şimdiye kadar yapılmış her bir CPU'da ve C dili standardı yıllarca gerekliyken, kız dillerinin çoğu işaret ve büyüklük veya bir tamamlayıcısını desteklemeye bile zahmet etmiyor.)

Algol bunun için farklı modlara sahip ve operatörleri boolean ve integral modda farklı şekilde yorumlayarak çalıştı. Yani, bitsel bir işlem tamsayı tiplerinde ve mantıksal bir işlem ise boolean tiplerde oldu.

BCPL ayrı bir boolean tipine sahipti, fakat hem bitsel hem de mantıksal olmayan tek bir notoperatör vardı . C'nin bu erken öncüsünün bu işi yapması:

Gerçek değerinin değeri, tamamıyla bunlardan oluşan bir bit desenidir; false değeri sıfırdır.

Bunu not et true = ~ false

( Değer teriminin , C-ailesi dillerinde tamamen farklı bir şey ifade etmek için geliştiğini göreceksiniz . Bugün C de “nesne temsili” diyoruz.)

Bu tanım, aynı makine dili komutunun kullanılmamasını mantıklı ve bitsel olarak sağlar. C o rotayı takip etseydi, üstbilgi dosyaları dünyanın üzerinde olduğunu söylerdi #define TRUE -1.

Ancak B programlama dili zayıf yazılmış ve boolean veya hatta kayan noktalı tipler yoktu. Her şey, inthalefi C'nin karşılığıydı . Bu, bir programın bir programın bir mantıksal değer olarak doğru veya yanlış bir değer dışında bir değer kullanması durumunda ne olduğunu tanımlaması için iyi bir fikirdir. İlk önce bir truthy ifadesini “sıfıra eşit değil” olarak tanımladı. Bu, üzerinde çalıştığı ve CPU sıfır bayrağı bulunan mini bilgisayarlarda etkiliydi.

O zamanlar bir alternatif vardı: aynı CPU'ların da olumsuz bir bayrağı vardı ve BCPL'nin gerçeği değeri -1 idi, bu nedenle B bunun yerine bütün negatif sayıları gerçeğe uygun, bütün negatif olmayan sayıları sahte olarak tanımlamış olabilirdi. (Bu yaklaşımın bir kalıntısı var: aynı zamanda aynı insanlar tarafından geliştirilen UNIX'teki birçok sistem çağrısı, tüm hata kodlarını negatif tam sayılar olarak tanımlar. Sistem çağrılarının çoğu başarısızlık durumunda birkaç farklı negatif değerden birini döndürür.) şükretmek: daha kötü olabilirdi!

Ama tanımlama TRUEolarak 1ve FALSEsıra 0B'de kimlik anlamına geliyordu true = ~ falseartık tutulan ve bitwise ve mantıksal ifadeleri arasındaki belirsizliği giderecek Algol izin güçlü yazarak düşmüştü. Bu, yeni bir mantıksal olmayan operatör gerektiriyordu ve tasarımcılar !, muhtemelen eşit değillerdi, çünkü !=eşit bir işaretten dikey bir çubuk gibi görünüyorlardı. Aynı sözleşmeyi takip etmediler &&ya da ||henüz olmadıklarından dolayı.

Muhtemelen, olmalıdır: &B'deki operatör tasarlandığı gibi bozuldu. B ve C, de 1 & 2 == FALSEolsa 1ve 2her iki truthy değerleri ve orada Cı ilave ederek kısmen düzeltilmesi çalıştı bir hata B'deki mantıksal işlemi ifade etmek için kolay bir yoldur &&ve ||ancak zamanda ana endişe oldu Sonunda çalışmak için kısa devre yapmak ve programların daha hızlı çalışmasını sağlamak. Bunun ispatı şudur ^^: hayır : 1 ^ 2Her ikisi de operandları truthy olsa da bir gerçeği değeridir, ancak kısa devreden faydalanamaz.


4
+1. Bence bu, bu operatörlerin evrimi etrafında oldukça iyi bir rehberli tur.
Steve

BTW, işaret / büyüklük ve tamamlayıcı makinelerde, girdi zaten boole edilmiş olsa bile ayrı bitsel ve mantıksal olumsuzlamalara gerek vardır. ~0(ayarlanan tüm bitler) birinin tamamlayıcı negatif sıfırıdır (veya bir tuzak gösterimi). İşaret / büyüklük ~0, maksimum büyüklük ile negatif bir sayıdır.
Peter Cordes

@PeterCordes Kesinlikle haklısın. Sadece ikisini tamamlayan makinelere odaklanmıştım çünkü çok daha önemliydi. Belki bir dipnot değerinde.
Davislor

Benim yorumumun yeterli olduğunu düşünüyorum, fakat evet, belki parantez içinde (1'in tamamlayıcısı veya işareti / büyüklüğü için işe yaramaz) iyi bir düzenleme olur.
Peter Cordes
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.