Cái nào tốt hơn để sử dụng trong số các câu dưới đây trong C?
static const int var = 5;
hoặc là
#define var 5
hoặc là
enum { var = 5 };
Cái nào tốt hơn để sử dụng trong số các câu dưới đây trong C?
static const int var = 5;
hoặc là
#define var 5
hoặc là
enum { var = 5 };
Câu trả lời:
Nó phụ thuộc vào những gì bạn cần giá trị cho. Bạn (và mọi người khác cho đến nay) đã bỏ qua phương án thứ ba:
static const int var = 5;
#define var 5
enum { var = 5 };
Bỏ qua các vấn đề về việc lựa chọn tên, sau đó:
Vì vậy, trong hầu hết các bối cảnh, hãy thích 'enum' hơn các lựa chọn thay thế. Mặt khác, các gạch đầu tiên và cuối cùng có thể là các yếu tố kiểm soát - và bạn phải suy nghĩ nhiều hơn nếu bạn cần thỏa mãn cả hai cùng một lúc.
Nếu bạn hỏi về C ++, thì bạn sẽ sử dụng tùy chọn (1) - hằng số tĩnh - mọi lúc.
enum
là chúng được triển khai như int
([C99] 6.7.2.2/3). Một #define
phép bạn chỉ định unsigned và dài với U
và L
hậu tố, và const
cho phép bạn đưa ra một loại. enum
có thể gây ra vấn đề với chuyển đổi loại thông thường.
enum
cũng không #define
sử dụng thêm không gian, mỗi se. Giá trị sẽ xuất hiện trong mã đối tượng như một phần của hướng dẫn thay vì được phân bổ lưu trữ trong phân đoạn dữ liệu hoặc trong heap hoặc trên ngăn xếp. Bạn sẽ có một số không gian được phân bổ cho static const int
, nhưng trình biên dịch có thể tối ưu hóa nó nếu bạn không lấy địa chỉ.
enum
s (và static const
): chúng không thể thay đổi. a define
có thể là #undefine
'd trong đó một enum
và static const
được cố định với giá trị đã cho.
Nói chung:
static const
Bởi vì nó tôn trọng phạm vi và là loại an toàn.
Thông báo trước duy nhất tôi có thể thấy: nếu bạn muốn biến có thể được xác định trên dòng lệnh. Vẫn còn một sự thay thế:
#ifdef VAR // Very bad name, not long enough, too general, etc..
static int const var = VAR;
#else
static int const var = 5; // default value
#endif
Bất cứ khi nào có thể, thay vì macro / dấu chấm lửng, hãy sử dụng phương án thay thế an toàn.
Nếu bạn thực sự CẦN đi với một macro (ví dụ: bạn muốn __FILE__
hoặc __LINE__
), thì bạn nên đặt tên cho macro của mình RẤT cẩn thận: trong quy ước đặt tên của nó, Boost đề xuất tất cả chữ hoa, bắt đầu bằng tên của dự án (ở đây BOOST_ ), trong khi lướt qua thư viện, bạn sẽ nhận thấy đây là (nói chung) theo sau là tên của khu vực cụ thể (thư viện) sau đó với một tên có ý nghĩa.
Nó thường làm cho tên dài :)
static
có địa chỉ được lấy vẫn còn; và nếu địa chỉ được lấy, người ta không thể sử dụng một #define
hoặc enum
(không có địa chỉ) ... vì vậy tôi thực sự không biết cách nào có thể được sử dụng. Nếu bạn có thể loại bỏ "đánh giá thời gian biên dịch", extern const
thay vào đó bạn có thể tìm kiếm .
#if
có thể thích hợp hơn #ifdef
cho các cờ boolean, nhưng trong trường hợp này, nó sẽ làm cho không thể định nghĩa var
như 0
từ dòng lệnh. Vì vậy, trong trường hợp này, #ifdef
có ý nghĩa hơn, miễn 0
là giá trị pháp lý cho var
.
Trong C, cụ thể? Trong C, câu trả lời đúng là: use #define
(hoặc, nếu thích hợp, enum
)
Mặc dù có các thuộc tính phạm vi và gõ của một const
đối tượng, nhưng trong const
các đối tượng thực tế trong C (trái ngược với C ++) không phải là hằng số thực và do đó thường vô dụng trong hầu hết các trường hợp thực tế.
Vì vậy, trong C, sự lựa chọn nên được xác định bằng cách bạn dự định sử dụng hằng số của mình. Ví dụ: bạn không thể sử dụng một const int
đối tượng làm case
nhãn (trong khi macro sẽ hoạt động). Bạn không thể sử dụng một const int
đối tượng làm độ rộng trường bit (trong khi macro sẽ hoạt động). Trong C89 / 90, bạn không thể sử dụng một const
đối tượng để chỉ định kích thước mảng (trong khi macro sẽ hoạt động). Ngay cả trong C99, bạn không thể sử dụng một const
đối tượng để chỉ định kích thước mảng khi bạn cần một mảng không phải là VLA .
Nếu điều này quan trọng với bạn thì nó sẽ quyết định lựa chọn của bạn. Hầu hết thời gian, bạn sẽ không có lựa chọn nào khác ngoài sử dụng #define
trong C. Và đừng quên một lựa chọn khác, đó là tạo ra các hằng số thực sự trong C - enum
.
Trong C ++, const
các đối tượng là các hằng thực sự, vì vậy trong C ++, hầu như luôn luôn tốt hơn để thích const
biến thể ( static
mặc dù không cần rõ ràng trong C ++).
const int
các đối tượng trong trường hợp nhãn là bất hợp pháp trong tất cả các phiên bản của ngôn ngữ C. (Tất nhiên, trình biên dịch của bạn có thể hỗ trợ miễn phí dưới dạng C ++ - như phần mở rộng ngôn ngữ.)
const
có nghĩa là chỉ đọc. const int r = rand();
là hoàn toàn hợp pháp.
constexpr
so với const
đặc biệt với các stl
thùng chứa như array
hoặc bitset
.
switch()
tuyên bố, không phải trong case
một. Tôi cũng vừa bị bắt gặp cái này
Sự khác biệt giữa static const
và #define
là cái trước sử dụng bộ nhớ và cái sau không sử dụng bộ nhớ để lưu trữ. Thứ hai, bạn không thể chuyển địa chỉ của a #define
trong khi bạn có thể chuyển địa chỉ của a static const
. Trên thực tế, nó phụ thuộc vào hoàn cảnh nào chúng ta phải chịu, chúng ta cần chọn một trong hai trường hợp này. Cả hai đều tốt nhất trong những hoàn cảnh khác nhau. Xin đừng cho rằng cái này tốt hơn cái kia ... :-)
Nếu đó là trường hợp, Dennis Ritchie sẽ giữ người tốt nhất một mình ... hahaha ... :-)
const
không sử dụng bộ nhớ. GCC (được thử nghiệm với 4.5.3 và một vài phiên bản mới hơn) dễ dàng tối ưu hóa const int
thành một chữ trực tiếp trong mã của bạn khi sử dụng -O3. Vì vậy, nếu bạn thực hiện phát triển nhúng RAM thấp (ví dụ: AVR), bạn có thể sử dụng C const một cách an toàn nếu bạn sử dụng GCC hoặc trình biên dịch tương thích khác. Tôi chưa thử nó nhưng mong Clang sẽ làm điều tương tự btw.
Trong C #define
là phổ biến hơn nhiều. Bạn có thể sử dụng các giá trị đó để khai báo kích thước mảng chẳng hạn:
#define MAXLEN 5
void foo(void) {
int bar[MAXLEN];
}
ANSI C không cho phép bạn sử dụng static const
s trong bối cảnh này theo như tôi biết. Trong C ++, bạn nên tránh các macro trong những trường hợp này. Bạn có thể viết
const int maxlen = 5;
void foo() {
int bar[maxlen];
}
và thậm chí bỏ qua static
vì liên kết nội bộ được ngụ ý bởi const
[chỉ trong C ++].
const int MY_CONSTANT = 5;
trong một tập tin và truy cập nó trong một tập tin extern const int MY_CONSTANT;
khác. Tôi không thể tìm thấy bất kỳ thông tin nào trong tiêu chuẩn (ít nhất là C99) về const
việc thay đổi hành vi mặc định "6.2.2: 5 Nếu việc khai báo một định danh cho một đối tượng có phạm vi và không có thông số lớp lưu trữ, thì liên kết của nó là bên ngoài".
bar
là một VLA (mảng có chiều dài thay đổi); trình biên dịch có khả năng tạo mã như thể độ dài của nó không đổi.
Một nhược điểm khác của const
C là bạn không thể sử dụng giá trị trong việc khởi tạo một giá trị khác const
.
static int const NUMBER_OF_FINGERS_PER_HAND = 5;
static int const NUMBER_OF_HANDS = 2;
// initializer element is not constant, this does not work.
static int const NUMBER_OF_FINGERS = NUMBER_OF_FINGERS_PER_HAND
* NUMBER_OF_HANDS;
Thậm chí điều này không làm việc với một const kể từ khi biên dịch không xem nó như là một hằng số:
static uint8_t const ARRAY_SIZE = 16;
static int8_t const lookup_table[ARRAY_SIZE] = {
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; // ARRAY_SIZE not a constant!
Tôi rất vui khi sử dụng đánh máy const
trong những trường hợp này, nếu không ...
static uint8_t const ARRAY_SIZE = 16;
tất cả của bạn đột nhiên không còn biên dịch có thể là một chút thách thức, đặc biệt là khi #define ARRAY_SIZE 256
bị chôn vùi mười lớp sâu trong một mạng lưới các tiêu đề rối. Đó là tất cả các tên mũ ARRAY_SIZE
đang yêu cầu rắc rối. Dự trữ ALL_CAPS cho các macro và không bao giờ xác định một macro không ở dạng ALL_CAPS.
const
. Điều này có thể được nâng cao hơn nữa!
Nếu bạn có thể nhận được ngay với nó, static const
có rất nhiều lợi thế. Nó tuân theo các nguyên tắc phạm vi bình thường, có thể nhìn thấy trong trình gỡ lỗi và thường tuân theo các quy tắc mà các biến tuân theo.
Tuy nhiên, ít nhất là trong tiêu chuẩn C ban đầu, nó không thực sự là một hằng số. Nếu bạn sử dụng #define var 5
, bạn có thể viết int foo[var];
dưới dạng khai báo, nhưng bạn không thể làm điều đó (ngoại trừ phần mở rộng trình biên dịch "với static const int var = 5;
. Đây không phải là trường hợp trong C ++, nơi static const
phiên bản có thể được sử dụng ở bất cứ đâu #define
phiên bản có thể, và tôi tin điều này cũng là trường hợp với C99.
Tuy nhiên, không bao giờ đặt tên một #define
hằng với tên viết thường. Nó sẽ ghi đè lên bất kỳ khả năng sử dụng tên đó cho đến khi kết thúc các đơn vị dịch. Các hằng số vĩ mô phải nằm trong không gian tên riêng của chúng, theo truyền thống là tất cả các chữ in hoa, có lẽ có tiền tố.
const
trong C99 vẫn không phải là một hằng số thực sự. Bạn có thể khai báo kích thước mảng bằng const
C99, nhưng chỉ vì C99 hỗ trợ Mảng độ dài biến. Vì lý do này, nó sẽ chỉ hoạt động khi VLAs được phép. Ví dụ: ngay cả trong C99, bạn vẫn không thể sử dụng a const
để khai báo kích thước của mảng thành viên trong a struct
.
const int
kích thước như thể đó là một C ++ const hoặc macro. Cho dù bạn muốn phụ thuộc vào độ lệch của GCC so với tiêu chuẩn tất nhiên là lựa chọn của bạn, tôi sẽ tự mình đi theo trừ khi bạn thực sự có thể sử dụng trình biên dịch khác ngoài GCC hoặc Clang, sau này có tính năng tương tự ở đây (đã thử nghiệm với Clang 3.7).
Đó là LUÔN LUÔN nên sử dụng const, thay vì #define. Đó là vì const được xử lý bởi trình biên dịch và #define bởi bộ tiền xử lý. Nó giống như #define tự nó không phải là một phần của mã (nói đại khái).
Thí dụ:
#define PI 3.1416
Tên PI biểu tượng có thể không bao giờ được nhìn thấy bởi trình biên dịch; nó có thể được gỡ bỏ bằng vi xử lý trước khi mã nguồn thậm chí đạt đến một trình biên dịch. Do đó, tên PI có thể không được nhập vào bảng ký hiệu. Điều này có thể gây nhầm lẫn nếu bạn gặp lỗi trong quá trình biên dịch liên quan đến việc sử dụng hằng số, bởi vì thông báo lỗi có thể tham khảo 3.1416, không phải PI. Nếu PI được xác định trong tệp tiêu đề bạn không viết, bạn sẽ không biết 3.1416 đó đến từ đâu.
Vấn đề này cũng có thể xuất hiện trong trình gỡ lỗi tượng trưng, bởi vì, một lần nữa, tên bạn đang lập trình có thể không có trong bảng ký hiệu.
Giải pháp:
const double PI = 3.1416; //or static const...
#define var 5
sẽ gây rắc rối cho bạn nếu bạn có những thứ như thế mystruct.var
.
Ví dụ,
struct mystruct {
int var;
};
#define var 5
int main() {
struct mystruct foo;
foo.var = 1;
return 0;
}
Bộ tiền xử lý sẽ thay thế nó và mã sẽ không được biên dịch. Vì lý do này, phong cách mã hóa truyền thống đề nghị tất cả các hằng số #define
sử dụng chữ in hoa để tránh xung đột.
Tôi đã viết chương trình thử nghiệm nhanh để chứng minh một sự khác biệt:
#include <stdio.h>
enum {ENUM_DEFINED=16};
enum {ENUM_DEFINED=32};
#define DEFINED_DEFINED 16
#define DEFINED_DEFINED 32
int main(int argc, char *argv[]) {
printf("%d, %d\n", DEFINED_DEFINED, ENUM_DEFINED);
return(0);
}
Điều này biên dịch với các lỗi và cảnh báo:
main.c:6:7: error: redefinition of enumerator 'ENUM_DEFINED'
enum {ENUM_DEFINED=32};
^
main.c:5:7: note: previous definition is here
enum {ENUM_DEFINED=16};
^
main.c:9:9: warning: 'DEFINED_DEFINED' macro redefined [-Wmacro-redefined]
#define DEFINED_DEFINED 32
^
main.c:8:9: note: previous definition is here
#define DEFINED_DEFINED 16
^
Lưu ý rằng enum đưa ra lỗi khi định nghĩa đưa ra cảnh báo.
Định nghĩa
const int const_value = 5;
không phải lúc nào cũng xác định một giá trị không đổi. Một số trình biên dịch (ví dụ tcc 0.9.26 ) chỉ phân bổ bộ nhớ được xác định với tên "const_value". Sử dụng mã định danh "const_value", bạn không thể sửa đổi bộ nhớ này. Nhưng bạn vẫn có thể sửa đổi bộ nhớ bằng cách sử dụng một định danh khác:
const int const_value = 5;
int *mutable_value = (int*) &const_value;
*mutable_value = 3;
printf("%i", const_value); // The output may be 5 or 3, depending on the compiler.
Điều này có nghĩa là định nghĩa
#define CONST_VALUE 5
là cách duy nhất để xác định một giá trị không đổi mà không thể sửa đổi bằng bất kỳ phương tiện nào.
#define
cũng có thể được sửa đổi, bằng cách chỉnh sửa mã máy.
5
. Nhưng người ta không thể thay đổi #define
vì đó là một vĩ mô tiền xử lý. Nó không tồn tại trong chương trình nhị phân. Nếu một người muốn sửa đổi tất cả những nơi CONST_VALUE
được sử dụng, người ta phải làm từng cái một.
#define CONST 5
, sau đó if (CONST == 5) { do_this(); } else { do_that(); }
, và trình biên dịch loại bỏ else
nhánh. Làm thế nào để bạn đề xuất chỉnh sửa mã máy để thay đổi CONST
thành 6?
#define
là nó không chống đạn.
#define
. Cách thực sự duy nhất để làm điều đó là chỉnh sửa mã nguồn và biên dịch lại.
Mặc dù câu hỏi là về số nguyên, nhưng đáng chú ý là #define và enums là vô dụng nếu bạn cần một cấu trúc hoặc chuỗi không đổi. Cả hai thường được truyền cho các chức năng như con trỏ. (Với các chuỗi cần thiết; với các cấu trúc, nó hiệu quả hơn nhiều.)
Đối với số nguyên, nếu bạn đang ở trong một môi trường nhúng với bộ nhớ rất hạn chế, bạn có thể cần phải lo lắng về nơi lưu trữ hằng số và cách truy cập vào nó. Trình biên dịch có thể thêm hai hằng số trong thời gian chạy, nhưng thêm hai #defines vào thời gian biên dịch. Hằng số #define có thể được chuyển đổi thành một hoặc nhiều lệnh MOV [ngay lập tức], có nghĩa là hằng số được lưu trữ hiệu quả trong bộ nhớ chương trình. Một hằng số const sẽ được lưu trữ trong phần .const trong bộ nhớ dữ liệu. Trong các hệ thống có kiến trúc Harvard, có thể có sự khác biệt về hiệu suất và mức sử dụng bộ nhớ, mặc dù chúng có thể nhỏ. Chúng có thể quan trọng đối với tối ưu hóa lõi cứng của các vòng bên trong.
Đừng nghĩ rằng có một câu trả lời cho "cái nào luôn tốt nhất" nhưng, như Matthieu nói
static const
là loại an toàn. #define
Mặc dù vậy, thú cưng lớn nhất của tôi là khi gỡ lỗi trong Visual Studio, bạn không thể xem biến. Nó đưa ra một lỗi mà biểu tượng không thể được tìm thấy.
Ngẫu nhiên, một giải pháp thay thế #define
, cung cấp phạm vi phù hợp nhưng hoạt động như một hằng số "thực", là "enum". Ví dụ:
enum {number_ten = 10;}
Trong nhiều trường hợp, thật hữu ích khi xác định các kiểu liệt kê và tạo các biến của các loại đó; nếu điều đó được thực hiện, trình gỡ lỗi có thể hiển thị các biến theo tên liệt kê của chúng.
Tuy nhiên, một cảnh báo quan trọng khi thực hiện điều đó: trong C ++, các kiểu liệt kê có khả năng tương thích hạn chế với các số nguyên. Ví dụ, theo mặc định, người ta không thể thực hiện số học theo chúng. Tôi thấy rằng đó là một hành vi mặc định tò mò cho enums; Mặc dù thật tuyệt khi có loại "enum nghiêm ngặt", với mong muốn có C ++ thường tương thích với C, tôi nghĩ rằng hành vi mặc định của loại "enum" nên có thể hoán đổi cho nhau với số nguyên.
int
, vì vậy "enum hack" không thể được sử dụng với các kiểu số nguyên khác. ( Loại liệt kê tương thích với một số loại số nguyên do triển khai, không nhất thiết int
, nhưng trong trường hợp này, loại này là ẩn danh nên không thành vấn đề.)
int
biến được liệt kê (mà trình biên dịch được phép thực hiện) và người ta cố gắng gán cho biến đó một thành viên của liệt kê riêng của mình. Tôi muốn các ủy ban tiêu chuẩn sẽ thêm các cách khai báo các kiểu số nguyên với ngữ nghĩa được chỉ định. BẤT K platform nền tảng nào, bất kể char
kích thước, có thể ví dụ như khai báo một loại sẽ bao bọc mod 65536, ngay cả khi trình biên dịch phải thêm nhiều AND R0,#0xFFFF
hướng dẫn hoặc tương đương.
uint16_t
, mặc dù tất nhiên đó không phải là một kiểu liệt kê. Sẽ thật tuyệt khi cho phép người dùng chỉ định loại số nguyên được sử dụng để biểu thị một kiểu liệt kê nhất định, nhưng bạn có thể đạt được nhiều hiệu ứng tương tự với một typedef
for uint16_t
và một chuỗi #define
s cho các giá trị riêng lẻ.
2U < -1L
là đúng và những nền tảng khác là sai và hiện tại chúng tôi bị mắc kẹt với thực tế là một số nền tảng sẽ thực hiện so sánh giữa uint32_t
và int32_t
như đã ký và một số là không dấu, nhưng điều đó không có nghĩa là Ủy ban không thể định nghĩa một người kế thừa tương thích hướng lên với C bao gồm các loại có ngữ nghĩa sẽ phù hợp với tất cả các trình biên dịch.
Một sự khác biệt đơn giản:
Lúc trước chế biến, hằng số được thay thế bằng giá trị của nó. Vì vậy, bạn không thể áp dụng toán tử dereference cho một định nghĩa, nhưng bạn có thể áp dụng toán tử dereference cho một biến.
Như bạn cho rằng, định nghĩa là const tĩnh nhanh hơn.
Ví dụ: có:
#define mymax 100
bạn không thể làm printf("address of constant is %p",&mymax);
.
nhưng có
const int mymax_var=100
bạn có thể làm printf("address of constant is %p",&mymax_var);
.
Để rõ ràng hơn, định nghĩa được thay thế bằng giá trị của nó ở giai đoạn tiền xử lý, vì vậy chúng tôi không có bất kỳ biến nào được lưu trữ trong chương trình. Chúng tôi chỉ có mã từ đoạn văn bản của chương trình nơi định nghĩa được sử dụng.
Tuy nhiên, đối với const tĩnh, chúng ta có một biến được phân bổ ở đâu đó. Đối với gcc, const tĩnh được phân bổ trong đoạn văn bản của chương trình.
Ở trên, tôi muốn nói về toán tử tham chiếu để thay thế sự bổ nhiệm bằng tham chiếu.
const
vòng loại. C không có hằng số Symbolica ngoài hằng số enum . A const int
là một biến. Bạn cũng nhầm lẫn ngôn ngữ và thực hiện cụ thể. Không có yêu cầu nơi đặt đối tượng. Và nó thậm chí không đúng với gcc: thông thường nó đặt const
các biến đủ điều kiện trong .rodata
phần. Nhưng điều đó phụ thuộc vào nền tảng mục tiêu. Và bạn có nghĩa là địa chỉ của nhà điều hành &
.
Chúng tôi đã xem xét mã trình biên dịch được sản xuất trên MBF16X ... Cả hai biến thể đều dẫn đến cùng một mã cho các hoạt động số học (ví dụ THÊM ngay lập tức).
Vì vậy, const int
được ưa thích cho các loại kiểm tra trong khi #define
là phong cách cũ. Có lẽ nó là trình biên dịch cụ thể. Vì vậy, kiểm tra mã lắp ráp sản xuất của bạn.
Tôi không chắc chắn nếu tôi đúng, nhưng trong sự kêu gọi quan điểm của tôi #define
giá trị d nhanh hơn nhiều so với việc gọi bất kỳ biến thông thường nào khác (hoặc giá trị const). Đó là bởi vì khi chương trình đang chạy và nó cần sử dụng một số biến được khai báo thông thường, nó cần phải nhảy đến vị trí chính xác trong bộ nhớ để lấy biến đó.
Ngược lại khi nó sử dụng #define
giá trị d, chương trình không cần phải chuyển sang bất kỳ bộ nhớ được phân bổ nào, nó chỉ lấy giá trị. Nếu #define myValue 7
và chương trình gọi myValue
, nó hoạt động giống hệt như khi nó chỉ gọi 7
.