Смежные строковые литералы объединяются, и это полезно двумя способами: макросы , которые объединяют строки, и визуализация многострочных строковых литералов , как у вас было выше. Сравните, как этот код будет выглядеть иначе:
char const help_message[] = "Usage: %s [options] files ...\n\nOptions include:\n --verbose -v Be verbose\n --help -h Print this help message\n --output -o Specify output file\n\n";
Представьте, что вы пытаетесь поддерживать это вместо этого. Кроме того, если вы используете многострочный строковый литерал, вы должны либо экранировать символы новой строки, либо иметь дело с тем, что использует исходный код, который может не быть '\n'
, и вам придется внимательно следить за отступами. Все это делает код в вашем примере лучше.
Вот пример макроса:
#define STRINGIZE_(v) #v
#define STRINGIZE(v) STRINGIZE_(v)
#define LOCATION __FILE__ ":" STRINGIZE(__LINE__)
#define MY_ASSERT(expr) do { \
if (!(expr)) \
some_function(LOCATION ": assertion failed in " \
__PRETTY_FUNCTION__ ": " #expr); \
} while (0)
(Есть альтернативы этому, такие как передача отдельных параметров и использование специфичного для GCC __PRETTY_FUNCTION__
, например, устарел , но все остальное удобно, и это достойный "реал" пример, имхо.)
Другие проблемы, поднятые в этом коде, чтобы знать:
- одиночный
#
- оператор преобразования строки препроцессора (##
- другой специальный оператор препроцессора для вставки токена)
- без второго макроса stringize, вы получите
"filename.c:__LINE__"
- использование do-while не прерывает if-else и требует, чтобы макрос использовался как выражение, а не как выражение
- предотвращение использования в качестве выражения не всегда полезно, но это то, что вы хотите для макросов типа assert
Пример взлома if-else:
if (cond) MY_ASSERT(blah);
else other();
Расширяется до:
if (cond) do { ... } while(0);
else other();
Вместо:
if (cond) if (...) ...;
else other();
Что неверно и удивительно:
if (cond) {
if (...) {
...;
}
else {
other();
}
}