你的函数签名需要是这样的:
const char * myFunction()
{
return "my String";
}
背景:
对于C和C++来说,这是非常基础的知识,但还需要更多的讨论。
在C(以及C++)中,字符串只是一个以零字节结尾的字节数组 - 因此术语“零终止字符串”用于表示这种特定类型的字符串。还有其他类型的字符串,但在C(以及C++)中,这种类型是语言本身固有的。其他语言(如Java、Pascal等)使用不同的方法来理解"my string"
。
如果您曾经使用过Windows API(它是用C++编写的),您会经常看到函数参数,例如:“LPCSTR lpszName”。'sz'部分代表了这种“零终止字符串”的概念:一个以空(/零)终止符结尾的字节数组。
澄清:
为了方便起见,在这个“介绍”中,我将“字节”和“字符”交替使用,因为这样学习更容易。请注意,还有其他方法(宽字符和多字节字符系统(mbcs))用于处理国际字符。UTF-8就是一种mbcs的例子。为了简单起见,我没有详细解释这些内容。
内存:
这意味着像"my string"
这样的字符串实际上使用了9+1(=10!)个字节。当您最终开始动态分配字符串时,这一点非常重要。
因此,如果没有这个“终止零”,你就没有字符串。你只有一个在内存中挂着的字符数组(也称为缓冲区)。
数据的持久性:
这种方式使用函数:
const char * myFunction()
{
return "my String";
}
int main()
{
const char* szSomeString = myFunction();
printf("%s", szSomeString);
}
...通常会导致随机的未处理异常/段错误等问题,特别是“在未来”。
简而言之,虽然我的答案是正确的——如果你以那种方式使用它,9次中有9次会导致程序崩溃,特别是如果你认为这是“好习惯”的话。总之:通常不是这样。
例如,想象一下将来的某个时候,字符串现在需要以某种方式进行操作。通常,编码人员会“走捷径”并(尝试)编写如下代码:
const char * myFunction(const char* name)
{
char szBuffer[255];
snprintf(szBuffer, sizeof(szBuffer), "Hi %s", name);
return szBuffer;
}
也就是说,你的程序会崩溃,因为编译器(可能)已经释放了
szBuffer
使用的内存,这时在
main()
函数中调用
printf()
。 (你的编译器应该事先警告你这样的问题。)
有两种方法可以返回不容易出错的字符串:
1. 返回具有一定生命周期的缓冲区(静态或动态分配)。在C++中使用“辅助类”(例如
std::string
)来处理数据的寿命(这需要更改函数的返回值),或者
2. 将缓冲区传递给函数,并填充其中的信息。
请注意,在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;
else
return months[month-1];
}
int main()
{
printf("%s", calculateMonth(2));
}
这里static
的作用(许多程序员不喜欢这种'分配'类型)是将字符串放入程序的数据段中。也就是说,它是永久分配的。
如果你转向C++,你会使用类似的策略:
class Foo
{
char _someData[12];
public:
const char* someFunction() const
{
return _someData;
}
}
...但如果你只是为自己编写代码(而不是为了与他人共享的库),使用辅助类(例如std::string
)可能更容易。
示例2,使用调用方定义的缓冲区:
这是传递字符串的更加“防傻”方法。返回的数据不受调用方的操纵。也就是说,示例1很容易被调用方滥用,并暴露应用程序故障。这种方式更安全(尽管使用了更多的代码):
void calculateMonth(int month, char* pszMonth, int buffersize)
{
const char* months[] = {"Jan", "Feb", "Mar" .... };
if (!pszMonth || buffersize<1)
return;
if (month<1 || month>12)
{
*pszMonth = '\0';
}
else
{
strncpy(pszMonth, months[month-1], buffersize-1);
}
pszMonth[buffersize-1] = '\0';
}
int main()
{
char month[16];
calculateMonth(3, month, sizeof(month));
printf("%s", month);
}
有很多原因,第二种方法更好,特别是当你编写要被其他人使用的库时(你不需要锁定特定的分配/释放方案,第三方无法破坏你的代码,你不需要链接到特定的内存管理库),但像所有代码一样,取决于你最喜欢哪个。因此,大多数人选择示例1,直到他们被烧过很多次,才拒绝再以那种方式编写它 ;)
免责声明:
我已经退休几年了,我的C语言现在有点生疏。这个演示代码应该可以在C中正确编译(它对任何C++编译器也是可以的)。
return 0
,但不包括C90。 - hrnt