Có khả năng chuyển đổi tên điều tra viên thành chuỗi trong C không?
Câu trả lời:
Một cách, làm cho bộ tiền xử lý thực hiện công việc. Nó cũng đảm bảo enums và chuỗi của bạn được đồng bộ.
#define FOREACH_FRUIT(FRUIT) \
FRUIT(apple) \
FRUIT(orange) \
FRUIT(grape) \
FRUIT(banana) \
#define GENERATE_ENUM(ENUM) ENUM,
#define GENERATE_STRING(STRING) #STRING,
enum FRUIT_ENUM {
FOREACH_FRUIT(GENERATE_ENUM)
};
static const char *FRUIT_STRING[] = {
FOREACH_FRUIT(GENERATE_STRING)
};
Sau khi bộ tiền xử lý hoàn tất, bạn sẽ có:
enum FRUIT_ENUM {
apple, orange, grape, banana,
};
static const char *FRUIT_STRING[] = {
"apple", "orange", "grape", "banana",
};
Sau đó, bạn có thể làm điều gì đó như:
printf("enum apple as a string: %s\n",FRUIT_STRING[apple]);
Nếu trường hợp sử dụng thực sự chỉ là in tên enum, hãy thêm các macro sau:
#define str(x) #x
#define xstr(x) str(x)
Sau đó làm:
printf("enum apple as a string: %s\n", xstr(apple));
Trong trường hợp này, có vẻ như macro hai cấp là không cần thiết, tuy nhiên, do cách chuỗi hoạt động trong C, nó là cần thiết trong một số trường hợp. Ví dụ: giả sử chúng ta muốn sử dụng #define với enum:
#define foo apple
int main() {
printf("%s\n", str(foo));
printf("%s\n", xstr(foo));
}
Đầu ra sẽ là:
foo
apple
Điều này là do str sẽ xâu chuỗi foo đầu vào thay vì mở rộng nó thành apple. Bằng cách sử dụng xstr, việc mở rộng macro được thực hiện trước, sau đó kết quả đó được chuỗi.
Xem Stringification để biết thêm thông tin.
#define GENERATE_ENUM(ENUM) PREFIX##ENUM,
Trong tình huống mà bạn có điều này:
enum fruit {
apple,
orange,
grape,
banana,
// etc.
};
Tôi muốn đặt cái này trong tệp tiêu đề nơi enum được định nghĩa:
static inline char *stringFromFruit(enum fruit f)
{
static const char *strings[] = { "apple", "orange", "grape", "banana", /* continue for rest of values */ };
return strings[f];
}
enumToString(apple)
hơn để gõ "apple"
? Không phải ở đâu cũng có kiểu an toàn. Trừ khi tôi thiếu thứ gì đó, những gì bạn đề xuất ở đây là vô nghĩa và chỉ thành công trong việc xáo trộn mã.
Không có cách nào đơn giản để đạt được điều này một cách trực tiếp. Nhưng P99 có các macro cho phép bạn tự động tạo loại chức năng như vậy:
P99_DECLARE_ENUM(color, red, green, blue);
trong tệp tiêu đề và
P99_DEFINE_ENUM(color);
trong một đơn vị biên dịch (tệp .c) sau đó sẽ thực hiện thủ thuật, trong ví dụ đó, hàm sau đó sẽ được gọi color_getname
.
Tôi đã tìm thấy một thủ thuật tiền xử lý C đang thực hiện công việc tương tự mà không cần khai báo chuỗi mảng chuyên dụng (Nguồn: http://userpage.fu-berlin.de/~ram/pub/pub_jf47ht81Ht/c_preprocessor_application_en ).
Sau phát minh của Stefan Ram, các enum tuần tự (chẳng hạn như không nêu rõ chỉ số enum {foo=-1, foo1 = 1}
) có thể được thực hiện giống như thủ thuật thiên tài này:
#include <stdio.h>
#define NAMES C(RED)C(GREEN)C(BLUE)
#define C(x) x,
enum color { NAMES TOP };
#undef C
#define C(x) #x,
const char * const color_name[] = { NAMES };
Điều này cho kết quả sau:
int main( void ) {
printf( "The color is %s.\n", color_name[ RED ]);
printf( "There are %d colors.\n", TOP );
}
Màu sắc là ĐỎ.
Có 3 màu.
Vì tôi muốn ánh xạ các định nghĩa mã lỗi thành chuỗi mảng, để tôi có thể nối định nghĩa lỗi thô vào mã lỗi (ví dụ "The error is 3 (LC_FT_DEVICE_NOT_OPENED)."
:), tôi đã mở rộng mã theo cách đó bạn có thể dễ dàng xác định chỉ mục cần thiết cho các giá trị enum tương ứng :
#define LOOPN(n,a) LOOP##n(a)
#define LOOPF ,
#define LOOP2(a) a LOOPF a LOOPF
#define LOOP3(a) a LOOPF a LOOPF a LOOPF
#define LOOP4(a) a LOOPF a LOOPF a LOOPF a LOOPF
#define LOOP5(a) a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF
#define LOOP6(a) a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF
#define LOOP7(a) a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF
#define LOOP8(a) a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF
#define LOOP9(a) a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF
#define LC_ERRORS_NAMES \
Cn(LC_RESPONSE_PLUGIN_OK, -10) \
Cw(8) \
Cn(LC_RESPONSE_GENERIC_ERROR, -1) \
Cn(LC_FT_OK, 0) \
Ci(LC_FT_INVALID_HANDLE) \
Ci(LC_FT_DEVICE_NOT_FOUND) \
Ci(LC_FT_DEVICE_NOT_OPENED) \
Ci(LC_FT_IO_ERROR) \
Ci(LC_FT_INSUFFICIENT_RESOURCES) \
Ci(LC_FT_INVALID_PARAMETER) \
Ci(LC_FT_INVALID_BAUD_RATE) \
Ci(LC_FT_DEVICE_NOT_OPENED_FOR_ERASE) \
Ci(LC_FT_DEVICE_NOT_OPENED_FOR_WRITE) \
Ci(LC_FT_FAILED_TO_WRITE_DEVICE) \
Ci(LC_FT_EEPROM_READ_FAILED) \
Ci(LC_FT_EEPROM_WRITE_FAILED) \
Ci(LC_FT_EEPROM_ERASE_FAILED) \
Ci(LC_FT_EEPROM_NOT_PRESENT) \
Ci(LC_FT_EEPROM_NOT_PROGRAMMED) \
Ci(LC_FT_INVALID_ARGS) \
Ci(LC_FT_NOT_SUPPORTED) \
Ci(LC_FT_OTHER_ERROR) \
Ci(LC_FT_DEVICE_LIST_NOT_READY)
#define Cn(x,y) x=y,
#define Ci(x) x,
#define Cw(x)
enum LC_errors { LC_ERRORS_NAMES TOP };
#undef Cn
#undef Ci
#undef Cw
#define Cn(x,y) #x,
#define Ci(x) #x,
#define Cw(x) LOOPN(x,"")
static const char* __LC_errors__strings[] = { LC_ERRORS_NAMES };
static const char** LC_errors__strings = &__LC_errors__strings[10];
Trong ví dụ này, bộ tiền xử lý C sẽ tạo ra mã sau :
enum LC_errors { LC_RESPONSE_PLUGIN_OK=-10, LC_RESPONSE_GENERIC_ERROR=-1, LC_FT_OK=0, LC_FT_INVALID_HANDLE, LC_FT_DEVICE_NOT_FOUND, LC_FT_DEVICE_NOT_OPENED, LC_FT_IO_ERROR, LC_FT_INSUFFICIENT_RESOURCES, LC_FT_INVALID_PARAMETER, LC_FT_INVALID_BAUD_RATE, LC_FT_DEVICE_NOT_OPENED_FOR_ERASE, LC_FT_DEVICE_NOT_OPENED_FOR_WRITE, LC_FT_FAILED_TO_WRITE_DEVICE, LC_FT_EEPROM_READ_FAILED, LC_FT_EEPROM_WRITE_FAILED, LC_FT_EEPROM_ERASE_FAILED, LC_FT_EEPROM_NOT_PRESENT, LC_FT_EEPROM_NOT_PROGRAMMED, LC_FT_INVALID_ARGS, LC_FT_NOT_SUPPORTED, LC_FT_OTHER_ERROR, LC_FT_DEVICE_LIST_NOT_READY, TOP };
static const char* __LC_errors__strings[] = { "LC_RESPONSE_PLUGIN_OK", "" , "" , "" , "" , "" , "" , "" , "" "LC_RESPONSE_GENERIC_ERROR", "LC_FT_OK", "LC_FT_INVALID_HANDLE", "LC_FT_DEVICE_NOT_FOUND", "LC_FT_DEVICE_NOT_OPENED", "LC_FT_IO_ERROR", "LC_FT_INSUFFICIENT_RESOURCES", "LC_FT_INVALID_PARAMETER", "LC_FT_INVALID_BAUD_RATE", "LC_FT_DEVICE_NOT_OPENED_FOR_ERASE", "LC_FT_DEVICE_NOT_OPENED_FOR_WRITE", "LC_FT_FAILED_TO_WRITE_DEVICE", "LC_FT_EEPROM_READ_FAILED", "LC_FT_EEPROM_WRITE_FAILED", "LC_FT_EEPROM_ERASE_FAILED", "LC_FT_EEPROM_NOT_PRESENT", "LC_FT_EEPROM_NOT_PROGRAMMED", "LC_FT_INVALID_ARGS", "LC_FT_NOT_SUPPORTED", "LC_FT_OTHER_ERROR", "LC_FT_DEVICE_LIST_NOT_READY", };
Điều này dẫn đến các khả năng triển khai sau:
LC_errors__strings [-1] ==> LC_errors__strings [LC_RESPONSE_GENERIC_ERROR] ==> "LC_RESPONSE_GENERIC_ERROR"
Bạn không cần phải dựa vào bộ xử lý trước để đảm bảo rằng enums và chuỗi của bạn được đồng bộ hóa. Đối với tôi, việc sử dụng macro có xu hướng làm cho mã khó đọc hơn.
enum fruit
{
APPLE = 0,
ORANGE,
GRAPE,
BANANA,
/* etc. */
FRUIT_MAX
};
const char * const fruit_str[] =
{
[BANANA] = "banana",
[ORANGE] = "orange",
[GRAPE] = "grape",
[APPLE] = "apple",
/* etc. */
};
Lưu ý: các chuỗi trong fruit_str
mảng không cần phải được khai báo theo thứ tự như các mục enum.
printf("enum apple as a string: %s\n", fruit_str[APPLE]);
Nếu bạn sợ quên một chuỗi, bạn có thể thêm kiểm tra sau:
#define ASSERT_ENUM_TO_STR(sarray, max) \
typedef char assert_sizeof_##max[(sizeof(sarray)/sizeof(sarray[0]) == (max)) ? 1 : -1]
ASSERT_ENUM_TO_STR(fruit_str, FRUIT_MAX);
Một lỗi sẽ được báo cáo tại thời điểm biên dịch nếu số lượng các mục enum không khớp với số lượng chuỗi trong mảng.
Một hàm như thế mà không xác thực enum là một điều nguy hiểm. Tôi đề nghị sử dụng một câu lệnh chuyển đổi. Một ưu điểm khác là điều này có thể được sử dụng cho các enum có các giá trị được xác định, ví dụ cho các cờ có giá trị là 1,2,4,8,16, v.v.
Cũng đặt tất cả các chuỗi enum của bạn lại với nhau trong một mảng: -
static const char * allEnums[] = {
"Undefined",
"apple",
"orange"
/* etc */
};
xác định các chỉ số trong tệp tiêu đề: -
#define ID_undefined 0
#define ID_fruit_apple 1
#define ID_fruit_orange 2
/* etc */
Làm điều này giúp bạn tạo các phiên bản khác nhau dễ dàng hơn, chẳng hạn như nếu bạn muốn tạo các phiên bản quốc tế của chương trình bằng các ngôn ngữ khác.
Sử dụng macro, cũng trong tệp tiêu đề: -
#define CASE(type,val) case val: index = ID_##type##_##val; break;
Tạo một hàm với câu lệnh switch, câu lệnh này sẽ trả về a const char *
bởi vì các chuỗi static khuyết điểm: -
const char * FruitString(enum fruit e){
unsigned int index;
switch(e){
CASE(fruit, apple)
CASE(fruit, orange)
CASE(fruit, banana)
/* etc */
default: index = ID_undefined;
}
return allEnums[index];
}
Nếu lập trình với Windows thì giá trị ID_ có thể là giá trị tài nguyên.
(Nếu sử dụng C ++ thì tất cả các hàm có thể có cùng tên.
string EnumToString(fruit e);
)
Một giải pháp thay thế đơn giản hơn cho câu trả lời "enums không theo trình tự" của Hokyo, dựa trên việc sử dụng các ký hiệu chỉ định để khởi tạo mảng chuỗi:
#define NAMES C(RED, 10)C(GREEN, 20)C(BLUE, 30)
#define C(k, v) k = v,
enum color { NAMES };
#undef C
#define C(k, v) [v] = #k,
const char * const color_name[] = { NAMES };
Tôi thường làm điều này:
#define COLOR_STR(color) \
(RED == color ? "red" : \
(BLUE == color ? "blue" : \
(GREEN == color ? "green" : \
(YELLOW == color ? "yellow" : "unknown"))))