Wszyscy zdecydowanie używaliśmy typedef
si #define
raz lub drugi. Dzisiaj, pracując z nimi, zacząłem się nad czymś zastanawiać.
Rozważ poniższe 2 sytuacje, aby użyć int
typu danych o innej nazwie:
typedef int MYINTEGER
i
#define MYINTEGER int
Podobnie jak w powyższej sytuacji, w wielu sytuacjach możemy bardzo dobrze osiągnąć coś za pomocą #define, a także zrobić to samo za pomocą typedef, chociaż sposoby, w jakie to robimy, mogą być zupełnie inne. #define może także wykonywać akcje MAKRO, których typedef nie może.
Chociaż podstawowy powód ich używania jest inny, to jak różni się ich działanie? Kiedy należy preferować jedno z nich, gdy można używać obu? Ponadto, czy jedna z nich jest szybsza niż druga w jakich sytuacjach? (np. #define jest dyrektywą preprocesora, więc wszystko odbywa się znacznie wcześniej niż podczas kompilacji lub uruchamiania).
źródło
Odpowiedzi:
A
typedef
jest ogólnie preferowane, chyba że istnieje jakiś dziwny powód, dla którego potrzebujesz makro.makra dokonują podstawienia tekstowego, co może znacznie naruszać semantykę kodu. Na przykład biorąc pod uwagę:
możesz legalnie napisać:
ponieważ
short MYINTEGER
rozwija się doshort int
.Z drugiej strony z typedef:
nazwa
MYINTEGER
jest inną nazwą typuint
, a nie tekstowym zamiennikiem słowa kluczowego „int”.Gorzej jest z bardziej skomplikowanymi typami. Na przykład biorąc pod uwagę:
a
,b
Ic
to wszystkie wskaźniki, aled
tochar
, ponieważ ostatni wiersz rozszerza się do:co jest równoważne z
(Typedefs dla typów wskaźników zwykle nie jest dobrym pomysłem, ale ilustruje to sens).
Kolejny dziwny przypadek:
źródło
typedef
, ale właściwego sposobu budowania makro, które robi coś z tym, co następuje, jest w użyciu argumentów:#define CHAR_PTR(x) char *x
. Spowoduje to co najmniej spowodowanie nieprawidłowego użycia kompilatora.const CHAR_PTR mutable_pointer_to_const_char; const char_ptr const_pointer_to_mutable_char;
Zdecydowanie największym problemem z makrami jest to, że nie mają one zasięgu. Już samo to uzasadnia użycie typedef. Ponadto jest to bardziej wyraźne semantycznie. Kiedy ktoś, kto czyta twój kod, zobaczy definicję, nie będzie wiedział, o co chodzi, dopóki go nie przeczyta i nie zrozumie całego makra. Typedef informuje czytelnika, że nazwa typu zostanie zdefiniowana. (Powinienem wspomnieć, że mówię o C ++. Nie jestem pewien co do zakresu typedef dla C, ale myślę, że jest podobnie).
źródło
Kod źródłowy jest napisany głównie dla innych programistów. Komputery używają skompilowanej wersji.
W tej perspektywie
typedef
ma znaczenie, które#define
nie.źródło
Odpowiedź Keitha Thompsona jest bardzo dobra i wraz z dodatkowymi uwagami Tamása Szelei na temat ustalania zakresu powinny zapewnić wszystkie potrzebne tła.
Zawsze powinieneś uważać makra za ostateczność zdesperowanych. Są pewne rzeczy, które możesz zrobić tylko z makrami. Nawet wtedy powinieneś myśleć długo i ciężko, jeśli naprawdę chcesz to zrobić. Ilość bólu związanego z debugowaniem, który może być spowodowany przez subtelnie uszkodzone makra, jest znaczna i będziesz musiał przeglądać wstępnie przetworzone pliki. Warto to zrobić tylko raz, aby poznać zakres problemu w C ++ - tylko rozmiar wstępnie przetworzonego pliku może przyciągnąć wzrok.
źródło
Oprócz wszystkich podanych już wcześniej uwag na temat zamiany zakresu i tekstu, #define również nie jest w pełni kompatybilny z typedef! Mianowicie, jeśli chodzi o wskaźniki funkcji.
To deklaruje typ,
fptr_t
który jest wskaźnikiem do funkcji typuvoid func(void)
.Nie można zadeklarować tego typu za pomocą makra.
#define fptr_t void(*)(void)
oczywiście nie zadziała. Będziesz musiał napisać coś niejasnego#define fptr_t(name) void(*name)(void)
, co tak naprawdę nie ma sensu w języku C, w którym nie mamy konstruktorów.Wskaźników tablicy nie można zadeklarować za pomocą #define:
typedef int(*arr_ptr)[10];
I chociaż C nie ma obsługi języka dla bezpieczeństwa typów, o których warto wspomnieć, innym przypadkiem, w którym typedef i #define nie są kompatybilne, jest przypadek podejrzanej konwersji typu. Kompilator i / lub narzędzie analizatora astatycznego może wyświetlać ostrzeżenia o takich konwersjach, jeśli użyjesz polecenia typedef.
źródło
char *(*(*foo())[])();
(foo
funkcja zwraca wskaźnik do tablicy wskaźników do funkcji zwracających wskaźnikchar
).Używaj narzędzia o najmniejszej mocy, która wystarcza do wykonania zadania, i narzędzia o największej liczbie ostrzeżeń. #define jest oceniane w preprocesorze, jesteś tam w dużej mierze sam. Typedef jest oceniany przez kompilator. Podawane są kontrole i typedef może definiować tylko typy, jak sama nazwa wskazuje. Więc w twoim przykładzie zdecydowanie wybierz typedef.
źródło
Oprócz zwykłych argumentów przeciw definicji, jak napisałbyś tę funkcję za pomocą makr?
Jest to oczywiście trywialny przykład, ale ta funkcja może sumować się z dowolną iteracyjną sekwencją do przodu typu, który implementuje dodawanie *. Typedefy używane w ten sposób są ważnym elementem programowania ogólnego .
* Właśnie to napisałem tutaj, pozostawiam kompilację jako ćwiczenie dla czytelnika :-)
EDYTOWAĆ:
Ta odpowiedź wydaje się być źródłem zamieszania, więc pozwólcie, że wyciągnę ją jeszcze bardziej. Jeśli zajrzysz do definicji wektora STL, zobaczysz coś podobnego do następującego:
Zastosowanie typedefs w standardowych kontenerach pozwala na ogólną funkcję (taką jak ta, którą utworzyłem powyżej) na odniesienie do tych typów. Funkcja „Suma” jest wzorowana na typie kontenera (
std::vector<int>
), a nie na typie przechowywanym wewnątrz kontenera (int
). Bez typedef odniesienie do tego typu wewnętrznego nie byłoby możliwe.Dlatego typedefs są kluczowe dla Modern C ++ i nie jest to możliwe w przypadku makr.
źródło
typedef
, a nie makra vs funkcje wbudowane.typedef
pasuje do filozofii C ++: wszystkie możliwe kontrole / zapewnienia w czasie kompilacji.#define
to tylko sztuczka preprocesora, która ukrywa wiele kompilacji semantycznej. Nie powinieneś martwić się o wydajność kompilacji bardziej niż o poprawność kodu.Tworząc nowy typ, definiujesz nową „rzecz”, którą manipuluje domena twojego programu. Możesz więc użyć tej „rzeczy” do komponowania funkcji i klas, a kompilatora możesz użyć do wykonywania kontroli statycznych. W każdym razie, ponieważ C ++ jest kompatybilny z C, istnieje wiele niejawnych konwersji między
int
typami opartymi na int, które nie generują ostrzeżeń. W tym przypadku nie masz pełnej mocy kontroli statycznych. Możesz jednak zastąpić swójtypedef
,enum
aby znaleźć niejawne konwersje. Np .: Jeśli taktypedef int Age;
, możesz zamienić naenum Age { };
i będziesz otrzymywać wszelkiego rodzaju błędy przez niejawne konwersje międzyAge
iint
.Kolejna rzecz:
typedef
może być w środkunamespace
.źródło
Używanie definicji zamiast typów jest kłopotliwe w innym ważnym aspekcie, a mianowicie w koncepcji cech typu. Rozważ różne klasy (pomyśl o standardowych kontenerach), z których wszystkie definiują określone typy czcionek. Możesz pisać kod ogólny, odwołując się do typedefs. Przykłady obejmują ogólne wymagania dotyczące kontenerów (standard c ++ 23.2.1 [container.requirements.general]), takie jak
Wszystko to nie da się wyrazić w sposób ogólny za pomocą makra, ponieważ nie ma zasięgu.
źródło
Nie zapomnij o konsekwencjach tego dla twojego debuggera. Niektóre debuggery nie radzą sobie zbyt dobrze z #define. Korzystaj z obu w debugerze, którego używasz. Pamiętaj, że poświęcisz więcej czasu na czytanie niż na pisanie.
źródło
„#define” zastąpi to, co napisałeś później, typedef utworzy typ. Więc jeśli chcesz mieć typ klienta - użyj typedef. Jeśli potrzebujesz makr - użyj definicji.
źródło