Ваша подпись функции должна быть:
const char * myFunction()
{
return "My String";
}
Задний план:
Это фундаментально для C и C ++, но мы не будем останавливаться на достигнутом.
В C (и C ++, если на то пошло) строка - это просто массив байтов, оканчивающийся нулевым байтом, поэтому термин «строка-ноль» используется для представления этого конкретного вида строки. Существуют и другие типы строк, но в C (и C ++) эта разновидность по сути понимается самим языком. Другие языки (Java, Pascal и т. Д.) Используют разные методологии для понимания «моей строки».
Если вы когда-либо использовали Windows API (который находится на C ++), вы довольно часто будете видеть параметры функции, такие как: «LPCSTR lpszName». Часть 'sz' представляет понятие 'нулевая строка': массив байтов с нулевым (/ нулевым) ограничителем.
Уточнение:
Ради этого «вступления» я использую слова «байты» и «символы» как синонимы, потому что так легче выучить. Имейте в виду, что существуют другие методы ( расширенные символы и многобайтовые системы символов ( mbcs )), которые используются для работы с международными символами. UTF-8 представляет собой пример MBCS. Ради вступления я все это незаметно «пропускаю».
Объем памяти:
Это означает, что строка типа «моя строка» фактически использует 9 + 1 (= 10!) Байтов. Это важно знать, когда вы наконец дойдете до динамического распределения строк.
Итак, без этого «завершающего нуля» у вас не будет строки. В памяти находится массив символов (также называемый буфером).
Долговечность данных:
Использование функции таким образом:
const char * myFunction()
{
return "My String";
}
int main()
{
const char* szSomeString = myFunction(); // Fraught with problems
printf("%s", szSomeString);
}
... как правило, приводит к случайным необработанным исключениям / ошибкам сегментов и т.п., особенно «в будущем».
Короче говоря, хотя мой ответ правильный - в 9 случаях из 10 вы получите программу, которая дает сбой, если вы используете ее таким образом, особенно если вы считаете, что это «хорошая практика». Вкратце: обычно это не так.
Например, представьте, что когда-нибудь в будущем строку нужно будет каким-то образом изменить. Как правило, программист выбирает легкий путь и (пытается) писать такой код:
const char * myFunction(const char* name)
{
char szBuffer[255];
snprintf(szBuffer, sizeof(szBuffer), "Hi %s", name);
return szBuffer;
}
То есть ваша программа выйдет из строя, потому что компилятор (может / не мог) освободил память, используемую к szBufferмоменту вызова printf()in main(). (Ваш компилятор также должен заранее предупредить вас о таких проблемах.)
Есть два способа вернуть строки, которые не так быстро прерываются.
- возвращающие буферы (статические или динамически выделенные), которые живут некоторое время. В C ++ используйте вспомогательные классы (например,
std::string) для обработки долговечности данных (что требует изменения возвращаемого значения функции), или
- передать буфер функции, которая заполняется информацией.
Обратите внимание, что невозможно использовать строки без указателей в C. Как я показал, они синонимы. Даже в C ++ с шаблонными классами всегда есть буферы (то есть указатели), используемые в фоновом режиме.
Итак, чтобы лучше ответить на (теперь измененный вопрос). (Наверняка будет множество «других ответов», которые могут быть предоставлены.)
Более безопасные ответы:
Пример 1 с использованием статически выделенных строк:
const char* calculateMonth(int month)
{
static char* months[] = {"Jan", "Feb", "Mar" .... };
static char badFood[] = "Unknown";
if (month<1 || month>12)
return badFood; // Choose whatever is appropriate for bad input. Crashing is never appropriate however.
else
return months[month-1];
}
int main()
{
printf("%s", calculateMonth(2)); // Prints "Feb"
}
Что делает здесь «статика» (многим программистам не нравится этот тип «распределения»), так это то, что строки помещаются в сегмент данных программы. То есть он размещен постоянно.
Если вы перейдете на C ++, вы будете использовать аналогичные стратегии:
class Foo
{
char _someData[12];
public:
const char* someFunction() const
{ // The final 'const' is to let the compiler know that nothing is changed in the class when this function is called.
return _someData;
}
}
... но, вероятно, проще использовать вспомогательные классы, например std::string, если вы пишете код для собственного использования (а не как часть библиотеки для совместного использования с другими).
Пример 2 с использованием буферов, определяемых вызывающим абонентом:
Это более надежный способ передачи строк. Возвращенные данные не подлежат манипуляции со стороны вызывающей стороны. То есть вызывающая сторона может легко злоупотребить примером 1 и подвергнуть вас ошибкам приложения. Таким образом, это намного безопаснее (хотя и требует большего количества строк кода):
void calculateMonth(int month, char* pszMonth, int buffersize)
{
const char* months[] = {"Jan", "Feb", "Mar" .... }; // Allocated dynamically during the function call. (Can be inefficient with a bad compiler)
if (!pszMonth || buffersize<1)
return; // Bad input. Let junk deal with junk data.
if (month<1 || month>12)
{
*pszMonth = '\0'; // Return an 'empty' string
// OR: strncpy(pszMonth, "Bad Month", buffersize-1);
}
else
{
strncpy(pszMonth, months[month-1], buffersize-1);
}
pszMonth[buffersize-1] = '\0'; // Ensure a valid terminating zero! Many people forget this!
}
int main()
{
char month[16]; // 16 bytes allocated here on the stack.
calculateMonth(3, month, sizeof(month));
printf("%s", month); // Prints "Mar"
}
Есть много причин, по которым второй метод лучше, особенно если вы пишете библиотеку для использования другими (вам не нужно блокировать конкретную схему распределения / освобождения, третьи стороны не могут взломать ваш код, и вам не нужно связываться с конкретной библиотекой управления памятью), но, как и весь код, вам решать, что вам больше всего нравится. По этой причине большинство людей выбирают пример 1, пока их не сожгут столько раз, что они отказываются писать его таким образом;)
Отказ от ответственности:
Я ушел на пенсию несколько лет назад, и теперь мой C немного заржавел. Этот демонстрационный код должен правильно компилироваться с C (хотя это нормально для любого компилятора C ++).