Votre signature de fonction doit être:
const char * myFunction()
{
return "My String";
}
Contexte:
C'est tellement fondamental pour C & C ++, mais peu de discussions supplémentaires devraient être en ordre.
En C (et C ++ d'ailleurs), une chaîne est juste un tableau d'octets terminé par un octet zéro - d'où le terme "chaîne-zéro" est utilisé pour représenter cette saveur particulière de chaîne. Il existe d'autres types de chaînes, mais en C (et C ++), cette saveur est intrinsèquement comprise par le langage lui-même. D'autres langages (Java, Pascal, etc.) utilisent différentes méthodologies pour comprendre "ma chaîne".
Si jamais vous utilisez l'API Windows (qui est en C ++), vous verrez assez régulièrement des paramètres de fonction comme: "LPCSTR lpszName". La partie 'sz' représente cette notion de 'string-zero': un tableau d'octets avec un terminateur nul (/ zéro).
Clarification:
Pour cette «intro», j'utilise les mots «octets» et «caractères» de manière interchangeable, car c'est plus facile à apprendre de cette façon. Sachez qu'il existe d'autres méthodes (caractères larges et systèmes de caractères multi-octets ( mbcs )) qui sont utilisées pour gérer les caractères internationaux. UTF-8 est un exemple de mbcs. Par souci d'intro, je «saute» tranquillement tout cela.
Mémoire:
Cela signifie qu'une chaîne comme "ma chaîne" utilise en fait 9 + 1 (= 10!) Octets. Il est important de savoir quand vous parvenez enfin à allouer des chaînes de manière dynamique.
Donc, sans ce «zéro final», vous n'avez pas de chaîne. Vous avez un tableau de caractères (également appelé tampon) en mémoire.
Longévité des données:
L'utilisation de la fonction de cette façon:
const char * myFunction()
{
return "My String";
}
int main()
{
const char* szSomeString = myFunction(); // Fraught with problems
printf("%s", szSomeString);
}
... vous amènera généralement avec des exceptions aléatoires non gérées / des défauts de segment et autres, en particulier `` sur la route ''.
En bref, bien que ma réponse soit correcte - 9 fois sur 10, vous vous retrouverez avec un programme qui plante si vous l'utilisez de cette façon, surtout si vous pensez que c'est une «bonne pratique» de le faire de cette façon. En bref: ce n'est généralement pas le cas.
Par exemple, imaginez dans le futur, la chaîne doit maintenant être manipulée d'une manière ou d'une autre. Généralement, un codeur `` empruntera le chemin facile '' et (essaiera) d'écrire du code comme celui-ci:
const char * myFunction(const char* name)
{
char szBuffer[255];
snprintf(szBuffer, sizeof(szBuffer), "Hi %s", name);
return szBuffer;
}
Autrement dit, votre programme plantera car le compilateur (peut / peut ne pas) avoir libéré la mémoire utilisée szBuffer
par le moment où le printf()
in main()
est appelé. (Votre compilateur doit également vous avertir de tels problèmes au préalable.)
Il existe deux façons de renvoyer des chaînes qui ne barfront pas si facilement.
- renvoyant des tampons (statiques ou alloués dynamiquement) qui durent un certain temps. En C ++, utilisez des 'classes d'assistance' (par exemple,
std::string
) pour gérer la longévité des données (ce qui nécessite de changer la valeur de retour de la fonction), ou
- passer un tampon à la fonction qui se remplit d'informations.
Notez qu'il est impossible d'utiliser des chaînes sans utiliser de pointeurs en C. Comme je l'ai montré, ils sont également. Même en C ++ avec des classes de modèle, des tampons (c'est-à-dire des pointeurs) sont toujours utilisés en arrière-plan.
Donc, pour mieux répondre à la (question maintenant modifiée). (Il y aura sûrement une variété d '«autres réponses» qui peuvent être fournies.)
Des réponses plus sûres:
Exemple 1, en utilisant des chaînes allouées statiquement:
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"
}
Ce que fait le «statique» ici (de nombreux programmeurs n'aiment pas ce type d '«allocation»), c'est que les chaînes sont placées dans le segment de données du programme. Autrement dit, il est alloué en permanence.
Si vous passez au C ++, vous utiliserez des stratégies similaires:
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;
}
}
... mais il est probablement plus facile d'utiliser des classes d'assistance, par exemple std::string
, si vous écrivez le code pour votre propre usage (et ne fait pas partie d'une bibliothèque à partager avec d'autres).
Exemple 2, utilisant des tampons définis par l'appelant:
C'est la manière la plus «infaillible» de passer des chaînes. Les données renvoyées ne sont pas sujettes à manipulation par l'appelant. Autrement dit, l'exemple 1 peut facilement être abusé par un appelant et vous exposer à des défauts d'application. De cette façon, c'est beaucoup plus sûr (bien qu'il utilise plus de lignes de code):
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"
}
Il y a de nombreuses raisons pour lesquelles la deuxième méthode est meilleure, en particulier si vous écrivez une bibliothèque pour être utilisée par d'autres (vous n'avez pas besoin de vous verrouiller dans un schéma d'allocation / désallocation particulier, les tiers ne peuvent pas casser votre code, et vous n'avez pas besoin de créer un lien vers une bibliothèque de gestion de mémoire spécifique), mais comme tout code, c'est à vous de décider de ce que vous préférez. Pour cette raison, la plupart des gens optent pour l'exemple 1 jusqu'à ce qu'ils aient été brûlés tellement de fois qu'ils refusent de l'écrire de cette façon;)
Avertissement:
J'ai pris ma retraite il y a plusieurs années et mon C est un peu rouillé maintenant. Ce code de démonstration devrait tous se compiler correctement avec C (c'est bien pour n'importe quel compilateur C ++).