#define vs const w Objective-C

81

Jestem nowy w Objective-C i mam kilka pytań dotyczących constdyrektywy wstępnego przetwarzania #define.

Po pierwsze stwierdziłem, że nie jest możliwe zdefiniowanie typu stałej za pomocą #define. Dlaczego?

Po drugie, czy są jakieś zalety używania jednego z nich nad drugim?

Wreszcie, który sposób jest bardziej wydajny i / lub bezpieczniejszy?

ObjectiveCoder
źródło

Odpowiedzi:

107

Po pierwsze stwierdziłem, że nie można zdefiniować typu stałej za pomocą #define. Dlaczego tak jest?

Dlaczego co? To nie prawda:

#define MY_INT_CONSTANT ((int) 12345)

Po drugie, czy są jakieś zalety używania jednego z nich nad drugim?

Tak. #definedefiniuje makro, które jest zastępowane jeszcze przed rozpoczęciem kompilacji. constpo prostu modyfikuje zmienną, aby kompilator oznaczył błąd, jeśli spróbujesz go zmienić. Są konteksty, w których możesz użyć a, #defineale nie możesz użyć const(chociaż staram się znaleźć taki, który używa najnowszego clang). Teoretycznie constzajmuje miejsce w pliku wykonywalnym i wymaga odniesienia do pamięci, ale w praktyce jest to nieistotne i może zostać zoptymalizowane przez kompilator.

consts są znacznie bardziej przyjazne dla kompilatorów i debugerów niż #defines. W większości przypadków jest to nadrzędny punkt, który należy wziąć pod uwagę przy podejmowaniu decyzji, którego z nich użyć.

Pomyślałem o kontekście, w którym możesz użyć, #defineale nie const. Jeśli masz stałą, której chcesz użyć w wielu .cplikach, #definepo prostu umieść ją w nagłówku. Z a constmusisz mieć definicję w pliku C i

// in a C file
const int MY_INT_CONST = 12345;

// in a header
extern const int MY_INT_CONST;

w nagłówku. MY_INT_CONSTnie może być używany jako rozmiar tablicy o zasięgu statycznym lub globalnym w żadnym pliku C, z wyjątkiem tego, w którym jest zdefiniowana.

Jednak w przypadku stałych całkowitych można użyć rozszerzenia enum. W rzeczywistości Apple robi to prawie zawsze. Ma to wszystkie zalety zarówno #defines, jak i consts, ale działa tylko dla stałych całkowitych.

// In a header
enum
{
    MY_INT_CONST = 12345,
};

Wreszcie, który sposób jest bardziej wydajny i / lub bezpieczniejszy?

#definejest bardziej wydajne w teorii, chociaż, jak powiedziałem, nowoczesne kompilatory prawdopodobnie zapewniają, że różnica jest niewielka. #definejest bezpieczniejszy, ponieważ próba przypisania do niego zawsze jest błędem kompilatora

#define FOO 5

// ....

FOO = 6;   // Always a syntax error

consts można oszukać, aby zostać przypisanym do, chociaż kompilator może generować ostrzeżenia:

const int FOO = 5;

// ...

(int) FOO = 6;     // Can make this compile

W zależności od platformy przypisanie może nadal zakończyć się niepowodzeniem w czasie wykonywania, jeśli stała jest umieszczona w segmencie tylko do odczytu i jest oficjalnie niezdefiniowanym zachowaniem zgodnie ze standardem C.

Osobiście w przypadku stałych całkowitych zawsze używam enums dla stałych innych typów, używam constchyba, że ​​mam bardzo dobry powód, aby tego nie robić.

JeremyP
źródło
Wiem, że jest stary, ale jednym wystąpieniem, w którym nie możesz użyć stałej, której możesz użyć definicji, jest „#define MY_NSNUM_CONSTANT @ 5”, czego nie można zrobić za pomocą „NSNumber * const MY_NSNUM_CONSTANT = @ 5”
shortstuffsushi
Twierdzę, że #define zajmuje więcej miejsca w pliku wykonywalnym niż const. Stała jest przechowywana raz, ale #define jest mnożona za każdym razem, gdy jej używasz, ponieważ jest to tylko zamiana tekstu. Ale ponieważ różnica jest nieznaczna, jestem niepotrzebnie pedantyczny.
Ryan Ballantyne
@RyanBallantyne Myślę, że możesz być odpowiedni do rzeczy takich jak stałe łańcuchowe, ale nie do stałych całkowitych, ponieważ nawet jeśli przechowujesz stałą w jednym miejscu, aby uzyskać do niej dostęp, potrzebujesz jej adresu, który jest co najmniej tak duży jak int. Byłbym jednak bardzo zdziwiony, gdyby w ogóle coś zmieniło w nowoczesnym kompilatorze.
JeremyP
16

Od programisty C:

A constto po prostu zmienna, której zawartości nie można zmienić.

#define name valuejednak jest to polecenie preprocesora, które zastępuje wszystkie wystąpienia namez value.

Na przykład, jeśli ty #define defTest 5, wszystkie wystąpienia defTestw twoim kodzie zostaną zastąpione przez 5podczas kompilacji.

MegaNairda
źródło
11

Ważne jest, aby zrozumieć różnicę między instrukcjami #define i const, które nie są przeznaczone do tych samych rzeczy.

const

constsłuży do generowania obiektu z żądanego typu, który po zainicjowaniu będzie stały. Oznacza to, że jest to obiekt w pamięci programu i może być używany tylko do odczytu. Obiekt jest generowany przy każdym uruchomieniu programu.

#define

#definejest używany w celu ułatwienia czytelności kodu i przyszłych modyfikacji. Używając definicji, maskujesz tylko wartość za nazwą. Dlatego podczas pracy z prostokątem można zdefiniować szerokość i wysokość za pomocą odpowiednich wartości. Wtedy w kodzie będzie łatwiejszy do odczytania, ponieważ zamiast liczb pojawią się nazwy.

Jeśli później zdecydujesz się zmienić wartość szerokości, będziesz musiał zmienić ją tylko w definicji zamiast nudnego i niebezpiecznego wyszukiwania / zamiany w całym pliku. Podczas kompilacji preprocesor zastąpi wszystkie zdefiniowane nazwy wartościami w kodzie. Dzięki temu nie musisz tracić czasu na ich używanie.

fBourgeois
źródło
7

Oprócz komentarzy innych osób, błędy przy użyciu #definesą bardzo trudne do debugowania, ponieważ preprocesor przechwytuje je przed kompilatorem.

Ed Heal
źródło
3

Ponieważ dyrektywy preprocesora są źle widziane, sugeruję użycie pliku const. Nie można określić typu z preprocesorem, ponieważ dyrektywa preprocesora jest rozwiązana przed kompilacją. Cóż, możesz, ale coś takiego:

#define DEFINE_INT(name,value) const int name = value;

i używaj go jako

DEFINE_INT(x,42) 

który byłby postrzegany przez kompilator jako

const int x = 42;

Po pierwsze stwierdziłem, że nie można zdefiniować typu stałej za pomocą #define. Dlaczego tak jest?

Możesz, zobacz mój pierwszy fragment.

Po drugie, czy są jakieś zalety używania jednego z nich nad drugim?

Generalnie posiadanie constdyrektywy zamiast preprocesora pomaga w debugowaniu, nie tak bardzo w tym przypadku (ale nadal pomaga).

Wreszcie, który sposób jest bardziej wydajny i / lub bezpieczniejszy?

Obie są równie wydajne. Powiedziałbym, że makro może być potencjalnie bezpieczniejsze, ponieważ nie można go zmienić w czasie wykonywania, podczas gdy zmienna może.

Luchian Grigore
źródło
Dlaczego po prostu wpisujesz const ...zamiast makra?
Ed Heal
1
@EdHeal nie. Mówiłem tylko, że możesz w odpowiedzi na pytanie „Okazało się, że nie można zdefiniować typu stałej za pomocą #define, dlaczego tak jest?”
Luchian Grigore
1
> pre-processor directives are frowned upon[potrzebne źródło]
Ben Leggiero
Myślę, że możesz użyć następującego typu: #define myValue ((double) 2). Jak rozumiem, preprocesor po prostu zastąpi „myValue” tym, co nastąpi po nim w instrukcji define, w tym typem info.
vomako
1

Użyłem już wcześniej #define, aby pomóc w utworzeniu większej liczby metod z jednej metody, na przykład jeśli mam coś takiego.

 // This method takes up to 4 numbers, we don't care what the method does with these numbers.
 void doSomeCalculationWithMultipleNumbers:(NSNumber *)num1 Number2:(NSNumber *)num2 Number3:(NSNumber *)num23 Number3:(NSNumber *)num3;

Ale chcę też mieć metodę, która przyjmuje tylko 3 liczby i 2 liczby, więc zamiast pisać dwie nowe metody, zamierzam użyć tej samej, używając #define.

 #define doCalculationWithFourNumbers(num1, num2, num3, num4) \
         doSomeCalculationWithMultipleNumbers((num1), (num2), (num3), (num4))

 #define doCalculationWithThreeNumbers(num1, num2, num3) \
         doSomeCalculationWithMultipleNumbers((num1), (num2), (num3), nil)

 #define doCalculationWithTwoNumbers(num1, num2) \
         doSomeCalculationWithMultipleNumbers((num1), (num2), nil, nil)

Myślę, że to całkiem fajna rzecz, wiem, że możesz od razu przejść do metody i po prostu wstawić zero w przestrzeniach, których nie chcesz, ale jeśli budujesz bibliotekę, jest to bardzo przydatne. Tak też jest

     NSLocalizedString(<#key#>, <#comment#>)
     NSLocalizedStringFromTable(<#key#>, <#tbl#>, <#comment#>)
     NSLocalizedStringFromTableInBundle(<#key#>, <#tbl#>, <#bundle#>, <#comment#>)

są skończone.

Podczas gdy nie wierzę, że można to zrobić za pomocą stałych. Ale stałe mają swoje zalety w stosunku do #define, na przykład nie można określić typu za pomocą #define, ponieważ jest to dyrektywa preprocesora, która jest rozwiązywana przed kompilacją, a jeśli pojawi się błąd z #define, trudniej jest wtedy debugować stałe. Oba mają zalety i wady, ale powiedziałbym, że wszystko zależy od programisty, z którym zdecydowałeś się użyć. Napisałem bibliotekę z oboma, używając #define, aby robić to, co pokazałem, i stałych do deklarowania zmiennych stałych, na których muszę określić typ.

Popeye
źródło