Zwykle w C musimy podać komputerowi rodzaj danych w deklaracji zmiennej. Np. W poniższym programie chcę wydrukować sumę dwóch liczb zmiennoprzecinkowych X i Y.
#include<stdio.h>
main()
{
float X=5.2;
float Y=5.1;
float Z;
Z=Y+X;
printf("%f",Z);
}
Musiałem poinformować kompilator o typie zmiennej X.
- Czy kompilator nie może sam określić typu
X
?
Tak, może to zrobić, jeśli to zrobię:
#define X 5.2
Mogę teraz napisać mój program bez informowania kompilatora o typie X
:
#include<stdio.h>
#define X 5.2
main()
{
float Y=5.1;
float Z;
Z=Y+X;
printf("%f",Z);
}
Widzimy więc, że język C ma jakąś funkcję, za pomocą której może samodzielnie określać rodzaj danych. W moim przypadku ustalono, że X
jest typu float.
- Dlaczego musimy wspomnieć o rodzaju danych, kiedy deklarujemy coś w main ()? Dlaczego kompilator nie może samodzielnie określić typu danych zmiennej
main()
tak jak w#define
.
c
variables
data-types
io
declarations
użytkownik106313
źródło
źródło
5.2
jest adouble
, więc pierwszy program zaokrągla podwójne literały dofloat
precyzji, następnie dodaje je jako liczby zmiennoprzecinkowe, podczas gdy drugi zaokrągla podwójną reprezentację 5.1 z powrotemdouble
i dodaje ją dodouble
wartości 5.2 za pomocądouble
dodawania, a następnie zaokrągla wynik tego obliczenia dofloat
precyzji . Ponieważ zaokrąglanie występuje w różnych miejscach, wynik może być inny. To tylko jeden przykład rodzajów zmiennych wpływających na zachowanie innego identycznego programu.#define X 5.2
,X
nie jest zmienną, ale stałą, więc jest dosłownie zamieniony na preprocesor,5.2
gdziekolwiek wspomnianoX
. Nie możesz ponownie przypisaćX
.auto
faktycznie robi to, co chcesz). Z drugiej strony, jeśli uważasz, że wiesz, co robi Twój kod i faktycznie wpisałeś coś innego, wpisywanie statyczne w ten sposób wcześniej wykryje błąd, zanim stanie się on ogromnym problemem. Każdy język zachowuje równowagę: pisanie statyczne, wnioskowanie typu, pisanie dynamiczne. W przypadku niektórych zadań dodatkowe pisanie jest naprawdę tego warte. Dla innych to marnotrawstwo.Odpowiedzi:
Porównujesz deklaracje zmiennych do
#define
s, co jest niepoprawne. Za pomocą a#define
tworzysz odwzorowanie między identyfikatorem a fragmentem kodu źródłowego. Preprocesor C dosłownie zastąpi wszelkie wystąpienia tego identyfikatora dostarczonym fragmentem kodu. Pisaniekończy się dla kompilatora tym samym, co pisanie
Pomyśl o tym jak o automatycznym kopiowaniu i wklejaniu.
Ponadto zmienne normalne można przypisać ponownie, a makra utworzonego za
#define
pomocą nie można (choć można to zmienić#define
). WyrażenieFOO = 7
byłoby błędem kompilatora, ponieważ nie możemy przypisać do „wartości”:40 + 2 = 7
jest nielegalne.Dlaczego więc w ogóle potrzebujemy typów? Najwyraźniej niektóre języki pozbywają się typów, jest to szczególnie powszechne w językach skryptowych. Zwykle mają jednak coś, co nazywa się „typowaniem dynamicznym”, w którym zmienne nie mają ustalonych typów, ale wartości mają. Jest to o wiele bardziej elastyczne, ale także mniej wydajne. C lubi wydajność, więc ma bardzo prostą i wydajną koncepcję zmiennych:
Jest pewien odcinek pamięci zwany „stosem”. Każda zmienna lokalna odpowiada obszarowi na stosie. Teraz pytanie brzmi, ile bajtów musi mieć ten obszar? W języku C każdy typ ma dobrze zdefiniowany rozmiar, za pomocą którego można wyszukiwać
sizeof(type)
. Kompilator musi znać typ każdej zmiennej, aby mógł zarezerwować odpowiednią ilość miejsca na stosie.Dlaczego stałe utworzone za
#define
pomocą adnotacji typu nie są potrzebne? Nie są przechowywane na stosie. Zamiast tego#define
tworzy fragmenty kodu źródłowego wielokrotnego użytku w nieco łatwiejszy do utrzymania sposób niż kopiowanie i wklejanie. Literały w kodzie źródłowym, takie jak"foo"
lub42.87
są przechowywane przez kompilator, albo jako instrukcje specjalne, albo w osobnej sekcji danych wynikowego pliku binarnego.Jednak literały mają typy. Dosłowny ciąg znaków to
char *
.42
jest,int
ale można go również stosować w przypadku krótszych typów (konwersja zwężająca).42.8
byłobydouble
. Jeśli masz dosłownym i chcesz go mieć inny typ (np aby lub ), a następnie można użyć przyrostków - list po dosłownym, że zmiany w jaki sposób traktuje kompilator, że dosłowne. W naszym przypadku możemy powiedzieć lub .42.8
float
42
unsigned long int
42.8f
42ul
Niektóre języki mają pisanie statyczne jak w C, ale adnotacje typu są opcjonalne. Przykładami są ML, Haskell, Scala, C #, C ++ 11 i Go. Jak to działa? Magia? Nie, nazywa się to „wnioskowaniem typu”. W C # i Go kompilator patrzy na prawą stronę zadania i wydedukuje typ tego zadania. Jest to dość proste, jeśli prawa strona jest dosłowna, np
42ul
. To oczywiste, jaki powinien być typ zmiennej. Inne języki mają również bardziej złożone algorytmy, które uwzględniają sposób użycia zmiennej. Na przykład jeśli takx/2
, tox
nie może być ciągiem, ale musi mieć jakiś typ liczbowy.źródło
#define
mamy stałą, która jest bezpośrednio konwertowana na kod binarny - jakkolwiek by nie był - i jest przechowywana w pamięci bez zmian.#define X 5.2
?printf
, wywołujesz niezdefiniowane zachowanie. Na moim komputerze ten fragment wypisuje za każdym razem inną wartość, w Ideone ulega awarii po wydrukowaniu zera.X*Y
Nie jest ważne, jeżeliX
iY
są wskaźnikami, ale to jest w porządku, jeśli są oneint
s;*X
nie jest poprawny, jeśliX
jestint
, ale jest w porządku, jeśli jest wskaźnikiem.X w drugim przykładzie nigdy nie jest zmiennoprzecinkowy. Nazywa się to makrem, zastępuje zdefiniowaną wartość makra „X” w źródle wartością. Czytelny artykuł na temat #define jest tutaj .
W przypadku dostarczonego kodu, przed kompilacją preprocesor zmienia kod
do
i to się kompiluje.
Oznacza to, że można również zastąpić te „wartości” kodem podobnym do kodu
lub nawet
źródło
#define FOO(...) { __VA_ARGS__ }
.Krótka odpowiedź brzmi: C potrzebuje typów ze względu na historię / reprezentację sprzętu.
Historia: C został opracowany na początku lat siedemdziesiątych i miał być językiem programowania systemów. Kod jest idealnie szybki i najlepiej wykorzystuje możliwości sprzętu.
Wnioskowanie typów w czasie kompilacji byłoby możliwe, ale i tak już wolne czasy kompilacji wzrosłyby (patrz kreskówka „kompilowania” XKCD. Dotyczyło to „hello world” przez co najmniej 10 lat po opublikowaniu C ). Wnioskowanie typów w czasie wykonywania nie byłoby zgodne z celami programowania systemów. Wnioskowanie w czasie wykonywania wymaga dodatkowej biblioteki wykonawczej. C pojawił się na długo przed pierwszym komputerem. Który miał 256 pamięci RAM. Nie gigabajty lub megabajty, ale kilobajty.
W twoim przykładzie, jeśli pominiesz typy
Następnie kompilator mógł z radością odkryć, że X i Y są zmiennoprzecinkowe i uczynić Z tym samym. W rzeczywistości nowoczesny kompilator również by się przekonał, że X i Y nie są potrzebne i po prostu ustawił Z na 10.3.
Załóżmy, że obliczenia są osadzone w funkcji. Autor funkcji może chcieć wykorzystać swoją wiedzę o sprzęcie lub rozwiązanym problemie.
Czy podwójne byłoby bardziej odpowiednie niż float? Zajmuje więcej pamięci i jest wolniejszy, ale dokładność wyniku byłaby wyższa.
Być może zwracana wartość funkcji może być int (lub long), ponieważ ułamki dziesiętne nie są ważne, chociaż konwersja z liczby zmiennoprzecinkowej na int nie jest bez kosztów.
Wartość zwracaną można również podwójnie zagwarantować, że liczba zmiennoprzecinkowa + liczba zmiennoprzecinkowa nie zostanie przelana.
Wszystkie te pytania wydają się bezcelowe dla ogromnej większości pisanego dzisiaj kodu, ale były niezbędne, gdy C został stworzony.
źródło
C nie ma wnioskowania o typie (tak się nazywa, gdy kompilator zgadnie typ zmiennej dla Ciebie), ponieważ jest stary. Został opracowany na początku lat siedemdziesiątych
Wiele nowszych języków ma systemy, które pozwalają używać zmiennych bez określania ich typu (ruby, javascript, python itp.)
źródło
true
Jestboolean
), a nie zmienne (np.var x
Mogą zawierać wartość dowolnego typu). Ponadto, wnioskowanie o typach dla takich prostych przypadków, jak te z pytania, było prawdopodobnie znane dziesięć lat przed wydaniem C.implicit none
w takim przypadku musisz zadeklarować typ.