Mam te dwa małe programy:
do
#include <stdio.h>
int main()
{
if (5) {
printf("true\n");
}
else {
printf("false\n");
}
return 0;
}
Jawa
class type_system {
public static void main(String args[]) {
if (5) {
System.out.println("true");
}
else {
System.out.println("false");
}
}
}
który zgłasza komunikat o błędzie:
type_system.java:4: error: incompatible types: int cannot be converted to boolean
if (5) {
^
1 error
Moje zrozumienie
Do tej pory rozumiałem ten przykład jako demonstrację różnych systemów typów. C jest słabiej wpisany i pozwala na konwersję z int na boolean bez błędów. Java jest silniej typowana i kończy się niepowodzeniem, ponieważ niedozwolona jest niejawna rozmowa.
Dlatego moje pytanie: gdzie źle zrozumiałem?
Czego nie szukam
Moje pytanie nie dotyczy złego stylu kodowania. Wiem, że to źle, ale interesuje mnie, dlaczego C na to pozwala, a Java nie. Dlatego interesuje mnie system typów języka, a konkretnie jego siła.
java
c
type-systems
toogley
źródło
źródło
int
wartościach. Bardziej właściwym przykładem byłbyif (pointer)
.Odpowiedzi:
1. C i Java to różne języki
Fakt, że zachowują się inaczej, nie powinien być strasznym zaskoczeniem.
2. C nie dokonuje żadnej konwersji z
int
nabool
Jak to możliwe? C nie miał nawet prawdziwego
bool
typu do konwersji do 1999 roku . C został stworzony w roku 1970 iif
była częścią zanim było nawet C, z powrotem, gdy była to tylko seria modyfikacji B 1 .if
nie był po prostuNOP
w C przez prawie 30 lat. Działał bezpośrednio na wartości liczbowe. Verbage w standardzie C ( link PDF ), nawet ponad dekadę po wprowadzeniubool
s do C, nadal określa zachowanieif
(p 148) i?:
(p 100) przy użyciu terminów „nierówne do 0” i „równe 0” ”zamiast boolowskich określeń„ prawda ”lub„ fałsz ”lub coś podobnego.Dogodnie ...
3. ... liczby są akurat tymi, na których działają instrukcje procesora.
JZ
iJNZ
są podstawowymi instrukcjami montażu x86 dla warunkowego rozgałęziania. Skróty to „ J ump if Z ero” i „ J ump if N ot Z ero”. Odpowiedniki dla PDP-11, przy czym C pochodzi sąBEQ
( " B Ranch jeśli EQ UAL ") iBNE
(" B Ranch jeśli N OT E qual").Instrukcje te sprawdzają, czy poprzednia operacja zakończyła się zerem, czy też nie i odpowiednio przeskakują (lub nie).
4. Java kładzie większy nacisk na bezpieczeństwo niż C kiedykolwiek 2
I mając na uwadze bezpieczeństwo, zdecydowali, że ograniczenie
if
doboolean
s było warte kosztów (zarówno wdrożenie takiego ograniczenia, jak i wynikające z tego koszty alternatywne).1. B w ogóle nie ma typów. Języki asemblera też ogólnie nie. Jednak B i asemblery radzą sobie doskonale z rozgałęzianiem.
2. Słowami Dennisa Ritchiego przy opisywaniu planowanych modyfikacji B, które stały się C (moje podkreślenie):
źródło
C 2011 Online Draft
Zauważ, że ta klauzula określa tylko, że wyrażenie kontrolne powinno mieć typ skalarny (
char
/short
/int
/long
/ itp.), A nie konkretnie typ boolowski. Gałąź jest wykonywana, jeśli wyrażenie kontrolne ma niezerową wartość.Porównaj to z
Specyfikacja języka Java SE 8
Java, OTOH, w szczególności wymaga, aby wyrażenie sterujące w
if
instrukcji było typu logicznego.Nie chodzi więc o pisanie słabe vs. mocne, chodzi o to, co każda definicja języka określa jako prawidłowe wyrażenie kontrolne.
Edytować
Co do tego, dlaczego języki różnią się pod tym względem, kilka punktów:
C pochodzi od B, który był językiem „beztypowym” - w zasadzie wszystko było 32- do 36-bitowym słowem (w zależności od sprzętu), a wszystkie operacje arytmetyczne były operacjami liczb całkowitych. System typu C był po kolei przykręcony, tak że ...
C nie miał wyraźnego typu logicznego aż do wersji językowej z 1999 roku. C po prostu postępował zgodnie z konwencją B polegającą na użyciu zera do reprezentowania
false
i niezerowego do reprezentowaniatrue
.Java wypada po C przez kilka dziesięcioleci i została zaprojektowana specjalnie w celu usunięcia niektórych niedociągnięć C i C ++. Bez wątpienia zaostrzenie ograniczenia wyrażenia kontrolnego w
if
instrukcji było częścią tego.Nie ma żadnego powodu, by spodziewać się jakieś dwa języki programowania robić rzeczy w ten sam sposób. Nawet języki tak blisko spokrewnione jak C i C ++ różnią się w kilka interesujących sposobów, na przykład, że możesz mieć legalne programy C, które nie są legalnymi programami C ++ lub są legalnymi programami C ++, ale o innej semantyce itp.
źródło
int
do,boolean
podczas gdy Java nie. Odpowiedź jest taka, że nie ma takiej konwersji w C. Języki są różne, ponieważ są różnymi językami.Wiele odpowiedzi wydaje się dotyczyć osadzonego wyrażenia przypisania, które znajduje się w wyrażeniu warunkowym. (Chociaż jest to znany rodzaj potencjalnej pułapki, w tym przypadku nie jest on źródłem komunikatu o błędzie Java).
Być może dzieje się tak, ponieważ OP nie opublikował rzeczywistego komunikatu o błędzie, a
^
daszek wskazuje bezpośrednio na=
operatora przypisania.Jednak kompilator wskazuje na to,
=
że to operator generuje wartość końcową (a zatem i typ końcowy) wyrażenia, które widzi warunek.Narzeka na testowanie wartości innej niż boolowska, z następującym rodzajem błędu:
Testowanie liczb całkowitych, choć czasem wygodne, jest uważane za potencjalną pułapkę, której projektanci Java chcą uniknąć. W końcu Java ma prawdziwy typ danych logicznych , którego C nie ma (nie ma typu logicznego) .
Odnosi się to również do wskaźników testowych języka C dla wartości zerowej / innej niż null przez
if (p) ...
iif (!p) ...
, których Java podobnie nie pozwala, a zamiast tego wymaga jawnego operatora porównania w celu uzyskania wymaganej wartości logicznej.źródło
if
stwierdzenie.bool b = ...; int v = 5 + b;
. Różni się to od języków z pełnym typem logicznym, których nie można używać w arytmetyce.int v = 5; float f = 2.0 + v;
jest to dozwolone w C._Bool
jest w rzeczywistości jednym ze standardowych typów całkowitych bez znaku, zgodnie z definicją podaną przez Standard (patrz 6.2.5 / 6).Twoje pytanie składa się z dwóch części:
Dlaczego Java nie konwertować
int
doboolean
?Sprowadza się to do tego, że Java ma być jak najbardziej jawna. Jest bardzo statyczny, bardzo „prosto w twarz” dzięki systemowi typów. Rzeczy, które są automatycznie rzutowane na czcionkę w innych językach, nie są tak w Javie. Ty też musisz pisać
int a=(int)0.5
. Konwersjafloat
naint
utraciłaby informacje; to samo, co konwersjaint
na,boolean
i dlatego byłaby podatna na błędy. Ponadto musieliby określić wiele kombinacji. Jasne, te rzeczy wydają się oczywiste, ale zamierzały popełnić błąd po stronie ostrożności.Aha, w porównaniu z innymi językami, Java była bardzo dokładna w specyfikacji, ponieważ kod bajtowy nie był tylko wewnętrznym szczegółem implementacji. Musieliby dokładnie sprecyzować wszelkie interakcje. Ogromne przedsięwzięcie.
Dlaczego
if
nie akceptuje innych typów niżboolean
?if
można idealnie zdefiniować jako pozwalające na inne typy niżboolean
. Może mieć definicję, która mówi, że następujące są równoważne:true
int != 0
String
z.length>0
null
(a nieBoolean
mają wartościfalse
).null
i którego metodaObject.check_if
(wymyślona przeze mnie na tę okazję) zwracatrue
.Nie zrobili tego; nie było takiej potrzeby i chcieli, aby był jak najbardziej solidny, statyczny, przezroczysty, łatwy do odczytania itp. Brak ukrytych funkcji. Ponadto implementacja byłaby dość złożona, jestem pewien, że musiałbym przetestować każdą wartość dla wszystkich możliwych przypadków, więc wydajność mogła również mieć niewielki wpływ (Java na komputerach tego dnia była powolna; pamiętaj, że tam nie było żadnych kompilatorów JIT z pierwszymi wydaniami, przynajmniej nie na komputerach, z których wtedy korzystałem).
Głębszy powód
Głębszym powodem może być fakt, że Java ma swoje prymitywne typy, dlatego system typów jest rozdarty między obiektami a prymitywami. Może gdyby tego uniknęli, sprawy potoczyłyby się inaczej. Zgodnie z regułami podanymi w poprzedniej sekcji musieliby jednoznacznie zdefiniować prawdziwość każdego pojedynczego elementu pierwotnego (ponieważ elementy podstawowe nie dzielą superklasy , a elementy podstawowe nie są dobrze zdefiniowane
null
). To szybko zamieniłoby się w koszmar.Perspektywy
Cóż, w końcu może to tylko preferencja projektantów języków. Każdy język wydaje się kręcić własną drogą ...
Na przykład Ruby nie ma prymitywnych typów. Wszystko, dosłownie wszystko, jest przedmiotem. Mają bardzo łatwy czas, upewniając się, że każdy obiekt ma określoną metodę.
Ruby szuka prawdziwości we wszystkich typach przedmiotów, które możesz na nią rzucić. Co ciekawe, nadal nie ma
boolean
typu (ponieważ nie ma prymitywów), a także nie maBoolean
klasy. Jeśli zapytasz, jaką klasętrue
ma wartość (łatwo dostępnatrue.class
), otrzymaszTrueClass
. Ta klasa faktycznie ma metody, a mianowicie 4 operatory dla booleanów (| & ^ ==
). Tutajif
uważa , że jego wartość falsey jest wtedy i tylko wtedy, gdy jest tofalse
albonil
(null
Ruby). Wszystko inne jest prawdą. Tak więc0
lub""
obie są prawdą.Byłoby dla nich trywialne stworzenie metody,
Object#truthy?
którą można by wdrożyć dla każdej klasy i zwrócić indywidualną prawdę. Na przykład,String#truthy?
mógł zostać zaimplementowany, aby był prawdziwy dla niepustych ciągów znaków lub w ogóle. Nie zrobili tego, chociaż Ruby jest antytezą Javy w większości działów (dynamiczne pisanie kaczek z mixinem, ponowne otwieranie klas i tak dalej).Co może być zaskakujące dla programisty Perla, który jest przyzwyczajony do
$value <> 0 || length($value)>0 || defined($value)
bycia prawdomównym. I tak dalej.Wpisz SQL zgodnie z konwencją, że
null
wewnątrz dowolnego wyrażenia automatycznie powoduje, że jest ono fałszywe, bez względu na wszystko. Tak(null==null) = false
. W Ruby(nil==nil) = true
. Szczęśliwe czasy.źródło
((int)3) * ((float)2.5)
jest dość dobrze zdefiniowany w Javie (tak jest7.5f
).int
nafloat
również traci informacje w ogóle. Czy Java również zabrania takiej niejawnej obsady?Oprócz innych dobrych odpowiedzi chciałbym porozmawiać o spójności między językami.
Kiedy myślimy o matematycznie czystym wyrażeniu if, rozumiemy, że warunek może być prawdziwy lub fałszywy, bez żadnej innej wartości. Każdy główny język programowania szanuje ten matematyczny ideał; jeśli podasz logiczną wartość prawda / fałsz instrukcji if, możesz spodziewać się spójnego, intuicyjnego zachowania przez cały czas.
Jak na razie dobrze. To właśnie implementuje Java i tylko to, co implementuje Java.
Inne języki starają się zapewnić udogodnienia dla wartości niebędących wartościami logicznymi. Na przykład:
n
jest liczbą całkowitą. Zdefiniuj terazif (n)
jako skrótif (n != 0)
.x
jest liczbą zmiennoprzecinkową. Zdefiniuj terazif (x)
jako skrótif (x != 0 && !isNaN(x))
.p
jest to typ wskaźnika. Zdefiniuj terazif (p)
jako skrótif (p != null)
.s
jest to typ ciągu. Teraz zdefiniujif (s)
byćif (s != null && s != "")
.a
jest typem tablicy. Teraz zdefiniujif (a)
byćif (a != null && a.length > 0)
.Pomysł zapewnienia krótkich testów if wydaje się dobry na powierzchni ... dopóki nie napotkasz różnic w projektach i opiniach:
if (0)
jest traktowane jako fałsz w C, Python, JavaScript; ale traktowane jak prawda w Ruby.if ([])
jest traktowane jako fałszywe w Pythonie, ale prawdziwe w JavaScript.Każdy język ma swoje własne dobre powody, by traktować wyrażenia w taki czy inny sposób. (Na przykład jedynymi wartościami fałszowania w Ruby są,
false
anil
zatem0
są prawdziwe).Java przyjęła wyraźny projekt, aby zmusić cię do podania wartości logicznej do instrukcji if. Jeśli pospiesznie przetłumaczyłeś kod z C / Ruby / Python na Javę, nie możesz pozostawić żadnych luźnych if-testów bez zmian; musisz jawnie zapisać warunek w Javie. Zatrzymanie się na chwilę i zastanowienie się może uchronić Cię przed niechlujnymi błędami.
źródło
x != 0
to to samo cox != 0 && !isNaN(x)
? Zwykle jest tos != null
dla wskaźników, ale coś innego dla wskaźników bez wskaźników.Cóż, każdy typ skalarny w C, wskaźnik, wartość logiczna (od C99), liczba (zmiennoprzecinkowa lub nie) i wyliczenie (ze względu na bezpośrednie mapowanie na liczby) ma „naturalną” wartość falsey, więc jest to wystarczające dla każdego wyrażenia warunkowego .
Java też je ma (nawet jeśli wskaźniki Java są nazywane referencjami i są mocno ograniczone), ale wprowadziło automatyczne boksowanie w Javie 5.0, które niedopuszczalnie zamazuje wody. Poza tym programiści Java podkreślają istotną wartość pisania na klawiaturze.
Jedyny błąd, który zrodził debatę czy ograniczenie wyrażenia warunkowe do logicznej typu jest literówka pisania zadanie gdzie porównano zamierzony sposób, który nie uwzględnione przez ograniczenie warunkową wyrażeń typu w ogóle , ale przez zabronienie używania nagiej przypisania wyrażania za swoją wartość.
Każdy nowoczesny kompilator C lub C ++ radzi sobie z tym łatwo, dając ostrzeżenie lub błąd w przypadku takich wątpliwych konstrukcji, jeśli zostanie o to poproszony.
W przypadkach, w których jest dokładnie to, co było zamierzone, dodanie nawiasów pomaga.
Podsumowując, ograniczenie do wartości logicznej (i odpowiednika w pudełku w Javie) wygląda na nieudaną próbę stworzenia klasy literówek spowodowaną wyborem
=
błędów kompilacji przypisania.źródło
==
z operandami boolowskimi, z których pierwszy jest zmienną (którą następnie można było literować=
) w środkuif
zdarza się znacznie rzadziej niż w przypadku innych typów, powiedziałbym, więc jest to tylko częściowo nieudana próba.""
,(Object) ""
,0.0
,-0.0
,NaN
, puste tablice, iBoolean.FALSE
? Zwłaszcza ostatni jest zabawny, ponieważ jest niepustym wskaźnikiem (prawda), który rozpakowuje się na fałsz. +++ Nienawidzę także pisaniaif (o != null)
, ale oszczędzanie mi kilku znaków każdego dnia i pozwalanie mi spędzić pół dnia na debugowaniu mojego „sprytnego” wyrazu nie jest dobrym interesem. To powiedziawszy, chciałbym zobaczyć jakiś sposób na Javę: bardziej hojne zasady, ale nie pozostawiające żadnych dwuznaczności.Dwa z celów projektowych Java:
Pozwól programistom skoncentrować się na problemie biznesowym. Uprość i mniej narażaj na błędy, np. Wyrzucanie elementów bezużytecznych, aby programiści nie musieli koncentrować się na wyciekach pamięci.
Przenośny między platformami, np. Działa na dowolnym komputerze z dowolnym procesorem.
Używanie przypisania jako wyrażenia było znane z powodowania wielu błędów z powodu literówek, więc w celu nr 1 powyżej nie jest dozwolone sposób, w jaki próbujesz go użyć.
Ponadto wnioskowanie, że jakakolwiek niezerowa wartość = prawda i dowolna wartość zero = fałsz niekoniecznie musi być przenośna (tak, wierzcie lub nie, niektóre systemy traktują 0 jako prawdę, a 1 jako fałsz ), więc w ramach celu nr 2 powyżej nie jest domyślnie dozwolony . Oczywiście nadal możesz przesyłać treści jawnie.
źródło
Na przykład, co robią inne języki: Swift wymaga wyrażenia typu, który obsługuje protokół „BooleanType”, co oznacza, że musi mieć metodę „boolValue”. Typ „bool” oczywiście obsługuje ten protokół i możesz tworzyć własne typy go obsługujące. Typy liczb całkowitych nie obsługują tego protokołu.
W starszych wersjach języka opcjonalne typy obsługiwały „BooleanType”, więc można było napisać „if x” zamiast „if x! = Zero”. Co sprawiło, że użycie „Opcjonalnego boolu” było bardzo mylące. Opcjonalny bool ma wartości zero, fałsz lub prawda, a „jeśli b” nie zostałby wykonany, gdyby b był zerowy, i wykonany, jeśli b był prawdziwy lub fałszywy. To nie jest już dozwolone.
I wydaje się, że jest jeden facet, który naprawdę nie lubi otwierać swoich horyzontów ...
źródło