Votre signature de fonction doit être :
const char * myFunction()
{
return "my String";
}
Contexte :
Il s'agit d'un élément fondamental du C et du C++, mais il convient d'en parler un peu plus longuement.
En C (et en C++ d'ailleurs), une chaîne de caractères n'est qu'un tableau d'octets terminé par un octet zéro - c'est pourquoi le terme "chaîne-zéro" est utilisé pour représenter cette forme particulière de chaîne de caractères. Il existe d'autres types de chaînes, mais en C (et C++), ce type de chaîne est compris de manière inhérente par le langage lui-même. D'autres langages (Java, Pascal, etc.) utilisent des méthodologies différentes pour comprendre les chaînes de caractères. "my string"
.
Si vous utilisez l'API Windows (qui est en C++), vous verrez 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 null (/zero).
Clarification :
Pour les besoins de cette "introduction", j'utilise indifféremment les mots "octets" et "caractères", parce qu'il est plus facile d'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és pour traiter les caractères internationaux. UTF-8 est un exemple de SCBM. Pour les besoins de l'introduction, je passe tranquillement sur tout cela.
Mémoire :
Cela signifie qu'une chaîne de caractères comme "my string"
utilise en fait 9+1 (=10 !) octets. Il est important de le savoir lorsque vous vous apprêtez enfin à allouer des chaînes de caractères de manière dynamique.
Ainsi, sans ce "zéro final", il n'y a pas de chaîne de caractères. Vous avez un tableau de caractères (également appelé tampon) qui traîne en mémoire.
Longévité des données :
L'utilisation de la fonction se fait de cette manière :
const char * myFunction()
{
return "my String";
}
int main()
{
const char* szSomeString = myFunction(); // Fraught with problems
printf("%s", szSomeString);
}
... vous conduira généralement à des exceptions non gérées aléatoires, à des erreurs de segment et à d'autres problèmes de ce type, en particulier "à terme".
En bref, bien que ma réponse soit correcte, 9 fois sur 10, vous finirez par avoir un programme qui se plante si vous l'utilisez de cette manière, surtout si vous pensez que c'est une "bonne pratique" de le faire de cette manière. En bref : ce n'est généralement pas le cas.
Par exemple, imaginons qu'à un moment donné dans le futur, la chaîne de caractères actuelle doive être manipulée d'une manière ou d'une autre. Généralement, un codeur va "prendre le chemin le plus facile" et (essayer) d'écrire un code comme celui-ci :
const char * myFunction(const char* name)
{
char szBuffer[255];
snprintf(szBuffer, sizeof(szBuffer), "Hi %s", name);
return szBuffer;
}
En d'autres termes, votre programme se plantera parce que le compilateur (peut ou ne peut pas) a libéré la mémoire utilisée par szBuffer
au moment où le printf()
en main()
est appelé. (Votre compilateur devrait également vous avertir de ces problèmes à l'avance).
Il y a deux façons de renvoyer des chaînes qui ne dégueulent pas si facilement.
- retourner des tampons (statiques ou alloués dynamiquement) qui restent en place pendant un certain temps. En C++, on utilise des "classes d'aide" (par exemple,
std::string
) pour gérer la longévité des données (ce qui nécessite de modifier la valeur de retour de la fonction), ou
- transmettre à la fonction une mémoire tampon qui sera remplie d'informations.
Notez qu'il est impossible d'utiliser des chaînes sans utiliser des pointeurs en C. Comme je l'ai montré, ils sont synonymes. Même en C++ avec les classes modèles, il y a toujours des tampons (c'est-à-dire des pointeurs) utilisés en arrière-plan.
Ainsi, pour mieux répondre à la question (maintenant modifiée). (Il y a certainement une variété d'"autres réponses" qui peuvent être fournies).
Des réponses plus sûres :
Exemple 1 : utilisation de chaînes de caractères allouées de manière statique :
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"
}
Quel est le static
(de nombreux programmeurs n'aiment pas ce type d'"allocation") est que les chaînes de caractères sont placées dans le segment de données du programme. En d'autres termes, elles sont allouées de manière permanente.
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'aide, telles que std::string
si vous écrivez le code pour votre propre usage (et non dans le cadre d'une bibliothèque destinée à être partagée avec d'autres).
Exemple 2 : utilisation de tampons définis par l'appelant :
C'est la façon la plus sûre de faire circuler les chaînes de caractères. Les données renvoyées ne sont pas susceptibles d'être manipulées par la partie appelante. En d'autres termes, l'exemple 1 peut facilement être utilisé de manière abusive par une partie appelante et vous exposer à des fautes d'application. Cette méthode est beaucoup plus sûre (bien qu'elle 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 destinée à être utilisée par d'autres (vous n'avez pas besoin de vous enfermer dans un schéma d'allocation/désallocation particulier, les tiers ne peuvent pas casser votre code, et vous n'avez pas besoin de vous lier à une bibliothèque de gestion de la mémoire spécifique), mais comme pour tout code, c'est à vous de choisir ce qui vous convient le mieux. 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 d'écrire de cette façon ;)
Avis de non-responsabilité :
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 se compiler correctement en C (il est cependant correct pour n'importe quel compilateur C++).