Twój podpis funkcji musi być:
const char * myFunction()
{
return "My String";
}
Tło:
Jest to tak fundamentalne dla C i C ++, ale trochę więcej dyskusji powinno być w porządku.
W C (i C ++ jeśli o to chodzi), łańcuch jest po prostu tablicą bajtów zakończoną bajtem zerowym - stąd termin „łańcuch zero” jest używany do reprezentowania tego szczególnego rodzaju ciągu. Istnieją inne rodzaje ciągów, ale w C (i C ++) ten smak jest z natury zrozumiały przez sam język. Inne języki (Java, Pascal itp.) Używają różnych metodologii do zrozumienia „mojego ciągu”.
Jeśli kiedykolwiek użyjesz Windows API (które jest w C ++), dość regularnie zobaczysz parametry funkcji, takie jak: "LPCSTR lpszName". Część „sz” reprezentuje pojęcie „string-zero”: tablica bajtów z zakończeniem zerowym (/ zero).
Wyjaśnienie:
Na potrzeby tego „wstępu” używam zamiennie słowa „bajty” i „znaki”, ponieważ w ten sposób łatwiej jest się nauczyć. Należy pamiętać, że istnieją inne metody (znaki szerokie i systemy znaków wielobajtowych ( mbcs )), które są używane do obsługi znaków międzynarodowych. UTF-8 to przykład pliku mbcs. Ze względu na intro po cichu „pomijam” to wszystko.
Pamięć:
Oznacza to, że ciąg taki jak „mój ciąg” w rzeczywistości wykorzystuje 9 + 1 (= 10!) Bajtów. Jest to ważne, aby wiedzieć, kiedy w końcu zajmiesz się dynamicznym przydzielaniem ciągów.
Tak więc bez tego „kończącego zera” nie masz łańcucha. Masz tablicę znaków (zwaną również buforem) wiszącą w pamięci.
Trwałość danych:
Korzystanie z funkcji w ten sposób:
const char * myFunction()
{
return "My String";
}
int main()
{
const char* szSomeString = myFunction(); // Fraught with problems
printf("%s", szSomeString);
}
... generalnie wyląduje z przypadkowymi nieobsługiwanymi wyjątkami / usterkami segmentów i tym podobnymi, zwłaszcza „w dół drogi”.
Krótko mówiąc, chociaż moja odpowiedź jest prawidłowa - 9 razy na 10 skończysz z programem, który ulega awarii, jeśli używasz go w ten sposób, zwłaszcza jeśli uważasz, że jest to „dobra praktyka”, aby robić to w ten sposób. Krótko mówiąc: generalnie nie.
Na przykład, wyobraź sobie kiedyś w przyszłości, że struną trzeba teraz w jakiś sposób manipulować. Ogólnie rzecz biorąc, programista `` pójdzie prostą ścieżką '' i (spróbuje) napisać kod w następujący sposób:
const char * myFunction(const char* name)
{
char szBuffer[255];
snprintf(szBuffer, sizeof(szBuffer), "Hi %s", name);
return szBuffer;
}
Oznacza to, że program będzie awarię ponieważ kompilator (może / nie może) wydali pamięć używaną przez szBufferprzez czas printf()w main()nazywa. (Twój kompilator powinien również wcześniej ostrzec Cię o takich problemach).
Istnieją dwa sposoby zwracania łańcuchów, które nie będą tak łatwo barfować.
- zwracające bufory (statyczne lub przydzielane dynamicznie), które istnieją przez jakiś czas. W C ++ użyj `` klas pomocniczych '' (na przykład
std::string) do obsługi długowieczności danych (co wymaga zmiany wartości zwracanej przez funkcję) lub
- przekazują bufor do funkcji, która jest wypełniana informacjami.
Zauważ, że nie można używać łańcuchów bez użycia wskaźników w C. Jak pokazałem, są one synonimami. Nawet w C ++ z klasami szablonów zawsze w tle używane są bufory (czyli wskaźniki).
Tak więc, aby lepiej odpowiedzieć na (teraz zmodyfikowane pytanie). (Z pewnością istnieje wiele „innych odpowiedzi”, których można udzielić).
Bezpieczniejsze odpowiedzi:
Przykład 1, używając statycznie przydzielonych ciągów:
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"
}
To, co robi tutaj „statyczny” (wielu programistów nie lubi tego typu „alokacji”), polega na tym, że ciągi znaków są umieszczane w segmencie danych programu. Oznacza to, że jest przydzielony na stałe.
Jeśli przejdziesz na C ++, zastosujesz podobne strategie:
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;
}
}
... ale prawdopodobnie łatwiej jest korzystać z klas pomocniczych, na przykład std::string, jeśli piszesz kod na własny użytek (a nie część biblioteki, którą można udostępniać innym).
Przykład 2, przy użyciu buforów zdefiniowanych przez wywołującego:
Jest to bardziej „niezawodny” sposób przekazywania sznurków. Zwrócone dane nie podlegają manipulacji ze strony dzwoniącego. Oznacza to, że przykład 1 może być łatwo nadużywany przez stronę dzwoniącą i narażać Cię na błędy aplikacji. W ten sposób jest dużo bezpieczniejszy (choć używa więcej linii kodu):
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"
}
Istnieje wiele powodów, dla których ta druga metoda jest lepsza, szczególnie jeśli piszesz bibliotekę do użytku przez innych (nie musisz blokować określonego schematu alokacji / zwalniania, osoby trzecie nie mogą złamać twojego kodu, i nie musisz łączyć się z konkretną biblioteką zarządzania pamięcią), ale jak w przypadku każdego kodu, to od Ciebie zależy, co lubisz najbardziej. Z tego powodu większość ludzi wybiera na przykład 1, dopóki nie zostały spalone tyle razy, że nie chcą już pisać tego w ten sposób;)
Zrzeczenie się:
Kilka lat temu przeszedłem na emeryturę i moje C jest teraz trochę zardzewiałe. Ten kod demonstracyjny powinien w całości skompilować się poprawnie w C (jest jednak w porządku dla każdego kompilatora C ++).