La firma de su función debe ser:
const char * myFunction()
{
return "My String";
}
Antecedentes:
Es tan fundamental para C & C ++, pero un poco más de discusión debería estar en orden.
En C (y C ++ para el caso), una cadena es solo una matriz de bytes terminados con un byte cero; de ahí que el término "cadena-cero" se use para representar este tipo particular de cadena. Hay otros tipos de cadenas, pero en C (& C ++), este sabor es inherentemente entendido por el lenguaje mismo. Otros lenguajes (Java, Pascal, etc.) utilizan diferentes metodologías para entender "mi cadena".
Si alguna vez usa la API de Windows (que está en C ++), verá parámetros de función con bastante regularidad como: "LPCSTR lpszName". La parte 'sz' representa esta noción de 'cadena-cero': una matriz de bytes con un terminador nulo (/ cero).
Aclaración:
Por el bien de esta 'introducción', utilizo la palabra 'bytes' y 'caracteres' indistintamente, porque es más fácil de aprender de esta manera. Tenga en cuenta que existen otros métodos (caracteres anchos y sistemas de caracteres multibyte ( mbcs )) que se utilizan para hacer frente a caracteres internacionales. UTF-8 es un ejemplo de mbcs. Por el bien de la introducción, silenciosamente 'salto' todo esto.
Memoria:
Esto significa que una cadena como "mi cadena" en realidad usa 9 + 1 (= 10!) Bytes. Es importante saber esto cuando finalmente pueda asignar cadenas de forma dinámica.
Entonces, sin este 'cero final', no tiene una cadena. Tiene una matriz de caracteres (también llamada búfer) en la memoria.
Longevidad de los datos:
El uso de la función de esta manera:
const char * myFunction()
{
return "My String";
}
int main()
{
const char* szSomeString = myFunction(); // Fraught with problems
printf("%s", szSomeString);
}
... generalmente lo llevará a fallas de segmento / excepciones no manejadas aleatorias y similares, especialmente 'en el camino'.
En resumen, aunque mi respuesta es correcta: 9 de cada 10 veces terminará con un programa que falla si lo usa de esa manera, especialmente si cree que es una 'buena práctica' hacerlo de esa manera. En resumen: generalmente no lo es.
Por ejemplo, imagine que en algún momento en el futuro, la cadena ahora debe manipularse de alguna manera. Generalmente, un codificador 'tomará el camino fácil' e (intentará) escribir código como este:
const char * myFunction(const char* name)
{
char szBuffer[255];
snprintf(szBuffer, sizeof(szBuffer), "Hi %s", name);
return szBuffer;
}
Es decir, su programa fallará porque el compilador (puede / puede que no) haya liberado la memoria utilizada szBufferen el momento printf()en que main()se invoca. (Su compilador también debería advertirle de estos problemas de antemano).
Hay dos formas de devolver cadenas que no vomitarán tan fácilmente.
- devolviendo búferes (estáticos o asignados dinámicamente) que viven por un tiempo. En C ++ use 'clases auxiliares' (por ejemplo,
std::string) para manejar la longevidad de los datos (lo que requiere cambiar el valor de retorno de la función), o
- pasar un búfer a la función que se completa con información.
Tenga en cuenta que es imposible utilizar cadenas sin utilizar punteros en C. Como he mostrado, son sinónimos. Incluso en C ++ con clases de plantilla, siempre se utilizan búferes (es decir, punteros) en segundo plano.
Entonces, para responder mejor a la (pregunta ahora modificada). (Seguramente habrá una variedad de 'otras respuestas' que se pueden proporcionar).
Respuestas más seguras:
Ejemplo 1, usando cadenas asignadas estáticamente:
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"
}
Lo que hace aquí lo 'estático' (a muchos programadores no les gusta este tipo de 'asignación') es que las cadenas se colocan en el segmento de datos del programa. Es decir, está asignado de forma permanente.
Si pasa a C ++, usará estrategias similares:
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;
}
}
... pero probablemente sea más fácil usar clases auxiliares, por ejemplo std::string, si está escribiendo el código para su propio uso (y no como parte de una biblioteca para compartir con otros).
Ejemplo 2, usando búferes definidos por el llamador:
Esta es la forma más 'infalible' de pasar cadenas. Los datos devueltos no están sujetos a manipulación por parte de la parte que llama. Es decir, una parte que llama puede abusar fácilmente del ejemplo 1 y exponerlo a fallas en la aplicación. De esta manera, es mucho más seguro (aunque usa más líneas de código):
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"
}
Hay muchas razones por las que el segundo método es mejor, especialmente si está escribiendo una biblioteca para que la utilicen otros (no es necesario que se bloquee en un esquema de asignación / desasignación particular, los terceros no pueden descifrar su código, y no es necesario que se vincule a una biblioteca de administración de memoria específica), pero como todo código, usted decide lo que más le guste. Por esa razón, la mayoría de las personas optan por el ejemplo 1 hasta que se han quemado tantas veces que se niegan a escribirlo de esa manera;)
Descargo de responsabilidad:
Me jubilé hace varios años y mi C está un poco oxidada ahora. Este código de demostración debería compilarse correctamente con C (aunque está bien para cualquier compilador de C ++).