B.name
атрибут класса, не атрибут экземпляра. Это обнаруживается в B.__dict__
, но не в b = B(); b.__dict__
.
различие затенено несколько, потому что при доступе к атрибуту на экземпляре класс dict является нейтрализацией. Таким образом в вышеупомянутом примере, b.name
даст Вам значение B.name
.
Для этого требуется, чтобы плоды были определены во внешнем файле. Это будет содержимое fruit.cpp :
#define FRUIT(name) name
enum Fruit {
#include "fruit-defs.h"
NUM_FRUITS
};
#undef FRUIT
#define FRUIT(name) #name
const char *Fruits [] = {
#include "fruit-defs.h"
NULL
};
#undef FRUIT
И это будет fruit-defs.h :
FRUIT(Banana),
FRUIT(Apple),
FRUIT(Pear),
FRUIT(Tomato),
Он работает, пока значения начинаются с 0 и равны последовательный ...
Обновление: смешайте это решение с решением Ричарда Пеннингтона, используя C99, если вам нужны непоследовательные значения. То есть, что-то вроде:
// This would be in fruit-defs.h
FRUIT(Banana, 7)
...
// This one for the enum
#define FRUIT(name, number) name = number
....
// This one for the char *[]
#define FRUIT(name, number) [number] = #name
Метод c99, который я нашел, помогает уменьшить количество ошибок:
enum Fruit {
APPLE,
BANANA
};
const char* Fruits[] = {
[APPLE] = "APPLE",
[BANANA] = "BANANA"
};
Вы можете добавлять перечисления, даже в середине, и не нарушать старые определения. Конечно, вы все еще можете получить NULL-строки для значений, которые вы забыли.
Одна уловка, которую я использовал в прошлом, - это добавить дополнительное перечисление, а затем выполнить утверждение времени компиляции (например, Boost ), чтобы убедиться, что два in sync:
enum Fruit {
APPLE,
BANANA,
// MUST BE LAST ENUM
LAST_FRUIT
};
const char *FruitNames[] =
{
"Apple",
"Banana",
};
BOOST_STATIC_ASSERT((sizeof(FruitNames) / sizeof(*FruitNames)) == LAST_FRUIT);
Это, по крайней мере, предотвратит то, что кто-то забудет добавить и к перечислению, и к массиву имен, и сообщит им, как только они попытаются скомпилировать.
Один комментарий к макросу - вам не нужен отдельный файл для счетчиков. Просто используйте другой макрос:
#define FRUITs \
FRUIT(Banana), \
FRUIT(Apple), \
FRUIT(Pear), \
FRUIT(Tomato)
(хотя я бы, вероятно, оставил запятые и при необходимости включил их в макрос FRUIT).
Что, если бы вы сделали что-то подобное?
enum Fruit {
Apple,
Banana,
NumFruits
};
const char *Fruits[NumFruits] = {
"Apple",
"Banana",
};
Затем, если вы добавите новую запись в перечисление Fruit, ваш компилятор должен пожаловаться на то, что в инициализаторе массива недостаточно записей, поэтому вы были бы вынуждены добавить запись в массив.
Таким образом, это защищает вас от неправильного размера массива, но не помогает гарантировать правильность строк.
Можно создать для него структуру классов:
class Fruit {
int value; char const * name ;
protected:
Fruit( int v, char const * n ) : value(v), name(n) {}
public:
int asInt() const { return value ; }
char const * cstr() { return name ; }
} ;
#define MAKE_FRUIT_ELEMENT( x, v ) class x : public Fruit { x() : Fruit( v, #x ) {} }
// Then somewhere:
MAKE_FRUIT_ELEMENT(Apple, 1);
MAKE_FRUIT_ELEMENT(Banana, 2);
MAKE_FRUIT_ELEMENT(Pear, 3);
Тогда у вас может быть функция, которая принимает Fruit, и она будет даже более безопасна по типу.
void foo( Fruit f ) {
std::cout << f.cstr() << std::endl;
switch (f.asInt()) { /* do whatever * } ;
}
Размер этой функции в 2 раза больше чем просто перечисление. Но, скорее всего, это не имеет значения.
Как показали другие люди, отвечавшие на вопрос, на самом деле не существует чистого («СУХОГО») способа сделать это с использованием только препроцессора C. Проблема в том, что вам нужно определить массив размера вашего перечисления, содержащий строки, соответствующие каждому значению перечисления, а препроцессор C недостаточно умен, чтобы иметь возможность это сделать. Что я делаю, так это создаю текстовый файл примерно так:
%status ok
%meaning
The routine completed its work successfully.
%
%status eof_reading_content
%meaning
The routine encountered the end of the input before it expected
to.
%
Вот разделители меток%.
Затем Perl-скрипт, рабочая часть которого выглядит так,
sub get_statuses
{
my ($base_name, $prefix) = @_;
my @statuses;
my $status_txt_file = "$base_name.txt";
my $status_text = file_slurp ($status_txt_file);
while ($status_text =~
m/
\%status\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\n
\%meaning\s*(.*?)\s*\n\%\s*\n
/gxs) {
my ($code, $meaning) = ($1, $2);
$code = $prefix."_$code";
$meaning =~ s/\s+/ /g;
push @statuses, [$code, $meaning];
}
return @statuses;
}
читает этот файл и записывает заголовок файл:
typedef enum kinopiko_status {
kinopiko_status_ok,
kinopiko_status_eof_reading_content,
и файл C:
/* Generated by ./kinopiko-status.pl at 2009-11-09 23:45. */
#include "kinopiko-status.h"
const char * kinopiko_status_strings[26] = {
"The routine completed its work successfully.",
"The routine encountered the end of the input before it expected to. ",
, используя входной файл вверху. Здесь также вычисляется число 26 путем подсчета входных строк. (На самом деле существует двадцать шесть возможных состояний.)
Затем создание файла строки состояния автоматизируется с помощью make
.
В общем, я не люблю макрорешения, хотя признаю, что их довольно сложно избежать.
Лично я выбрал собственный класс для обертывания перечислений. Цель заключалась в том, чтобы предложить немного больше, чем традиционные перечисления (например, итерация).
Под прикрытием я использую std :: map
для сопоставления перечисления с его std :: string
] коллега. Затем я могу использовать это как для итерации по перечислению, так и для "красивой печати" моего перечисления или инициализации его из строки, прочитанной в файле.
Проблема, конечно, заключается в определении, поскольку я должен сначала объявить перечисление а затем сопоставить его ... но это цена, которую вы платите за их использование.
Кроме того, я затем использую не настоящее перечисление,