ลายเซ็นฟังก์ชันของคุณต้องเป็น:
const char * myFunction()
{
return "My String";
}
พื้นหลัง:
เป็นพื้นฐานสำหรับ C & C ++ แต่ควรมีการอภิปรายเพิ่มเติมเล็กน้อย
ใน C (& C ++ สำหรับเรื่องนั้น) สตริงเป็นเพียงอาร์เรย์ของไบต์ที่สิ้นสุดด้วยศูนย์ไบต์ดังนั้นคำว่า "string-zero" จึงถูกใช้เพื่อแสดงถึงรสชาติเฉพาะของสตริงนี้ มีสตริงประเภทอื่น ๆ แต่ในภาษา C (& C ++) รสชาตินี้เข้าใจโดยเนื้อแท้ของภาษา ภาษาอื่น ๆ (Java, Pascal ฯลฯ ) ใช้วิธีการที่แตกต่างกันเพื่อทำความเข้าใจ "สตริงของฉัน"
หากคุณเคยใช้ Windows API (ซึ่งอยู่ใน C ++) คุณจะเห็นพารามิเตอร์การทำงานที่ค่อนข้างสม่ำเสมอเช่น: "LPCSTR lpszName" ส่วน 'sz' แสดงถึงแนวคิดของ 'string-zero': อาร์เรย์ของไบต์ที่มีตัวยุติเป็น null (/ ศูนย์)
ชี้แจง:
เพื่อประโยชน์ของ 'คำนำ' นี้ฉันใช้คำว่า 'ไบต์' และ 'อักขระ' แทนกันได้เพราะมันง่ายกว่าที่จะเรียนรู้ด้วยวิธีนี้ โปรดทราบว่ามีวิธีการอื่น ๆ (ระบบอักขระแบบกว้างและอักขระหลายไบต์ ( mbcs )) ที่ใช้ในการจัดการกับอักขระสากล UTF-8เป็นตัวอย่างของ mbcs เพื่อประโยชน์ในการแนะนำฉัน 'ข้าม' ทั้งหมดนี้ไปอย่างเงียบ ๆ
หน่วยความจำ:
ซึ่งหมายความว่าสตริงอย่าง "my string" ใช้ไบต์ 9 + 1 (= 10!) นี่เป็นสิ่งสำคัญที่ต้องรู้ว่าเมื่อใดที่คุณจะจัดสรรสตริงแบบไดนามิกได้ในที่สุด
ดังนั้นหากไม่มี "การยกเลิกศูนย์" นี้คุณจะไม่มีสตริง คุณมีอาร์เรย์ของอักขระ (หรือที่เรียกว่าบัฟเฟอร์) อยู่รอบ ๆ ในหน่วยความจำ
อายุการใช้งานของข้อมูล:
การใช้ฟังก์ชันด้วยวิธีนี้:
const char * myFunction()
{
return "My String";
}
int main()
{
const char* szSomeString = myFunction(); // Fraught with problems
printf("%s", szSomeString);
}
... โดยทั่วไปจะนำคุณไปสู่ข้อผิดพลาดที่ไม่สามารถจัดการได้แบบสุ่ม / ข้อผิดพลาดส่วนและสิ่งที่คล้ายกันโดยเฉพาะอย่างยิ่ง 'ลงข้างทาง'
ในระยะสั้นแม้ว่าคำตอบของฉันจะถูกต้อง - 9 ครั้งจาก 10 ครั้งคุณจะพบว่าโปรแกรมหยุดทำงานหากคุณใช้วิธีนี้โดยเฉพาะอย่างยิ่งถ้าคุณคิดว่าเป็น 'แนวปฏิบัติที่ดี' ที่จะทำเช่นนั้น ในระยะสั้น: โดยทั่วไปแล้วไม่ใช่
ตัวอย่างเช่นลองนึกภาพว่าในอนาคตตอนนี้สตริงต้องได้รับการปรับแต่งอย่างใดอย่างหนึ่ง โดยทั่วไป coder จะ 'ใช้เส้นทางที่ง่าย' และ (พยายาม) เขียนโค้ดดังนี้:
const char * myFunction(const char* name)
{
char szBuffer[255];
snprintf(szBuffer, sizeof(szBuffer), "Hi %s", name);
return szBuffer;
}
นั่นคือโปรแกรมของคุณจะผิดพลาดเพราะคอมไพเลอร์ (อาจ / อาจจะไม่) ได้เปิดตัวหน่วยความจำที่ใช้โดยszBuffer
ตามเวลาที่printf()
อยู่ในmain()
ที่เรียกว่า (คอมไพเลอร์ของคุณควรเตือนคุณถึงปัญหาดังกล่าวล่วงหน้า)
มีสองวิธีในการส่งคืนสตริงที่จะไม่ทำให้เกิดความเสียหาย
- ส่งคืนบัฟเฟอร์ (คงที่หรือจัดสรรแบบไดนามิก) ที่มีชีวิตอยู่ชั่วขณะ ใน C ++ ให้ใช้ 'คลาสตัวช่วย' (ตัวอย่างเช่น
std::string
) เพื่อจัดการกับอายุการใช้งานของข้อมูล (ซึ่งต้องเปลี่ยนค่าส่งคืนของฟังก์ชัน) หรือ
- ส่งผ่านบัฟเฟอร์ไปยังฟังก์ชันที่กรอกข้อมูล
โปรดทราบว่าเป็นไปไม่ได้ที่จะใช้สตริงโดยไม่ใช้พอยน์เตอร์ใน 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; // 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"
}
สิ่งที่ 'คงที่' ทำที่นี่ (โปรแกรมเมอร์หลายคนไม่ชอบ 'การจัดสรร' ประเภทนี้) คือสตริงจะถูกใส่เข้าไปในส่วนข้อมูลของโปรแกรม นั่นคือมันถูกจัดสรรอย่างถาวร
หากคุณย้ายไปที่ C ++ คุณจะใช้กลยุทธ์ที่คล้ายกัน:
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;
}
}
... แต่อาจจะง่ายกว่าที่จะใช้คลาสตัวช่วยเช่นstd::string
หากคุณกำลังเขียนโค้ดสำหรับการใช้งานของคุณเอง (ไม่ใช่ส่วนหนึ่งของไลบรารีที่จะแชร์กับผู้อื่น)
ตัวอย่างที่ 2 โดยใช้บัฟเฟอร์ที่กำหนดผู้โทร:
นี่เป็นวิธีที่ 'เข้าใจผิดได้มากกว่า' ในการส่งผ่านสตริงไปรอบ ๆ ข้อมูลที่ส่งคืนไม่ได้อยู่ภายใต้การจัดการโดยฝ่ายที่โทร นั่นคือตัวอย่างที่ 1 อาจถูกฝ่ายที่โทรเข้ามาใช้ในทางที่ผิดและทำให้คุณได้รับความผิดพลาดของแอปพลิเคชัน วิธีนี้จะปลอดภัยกว่ามาก (แม้ว่าจะใช้โค้ดมากกว่า):
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"
}
มีสาเหตุหลายประการที่วิธีที่สองดีกว่าโดยเฉพาะอย่างยิ่งถ้าคุณกำลังเขียนไลบรารีเพื่อให้ผู้อื่นใช้ (คุณไม่จำเป็นต้องล็อกเข้ากับรูปแบบการจัดสรร / ยกเลิกการจัดสรรเฉพาะบุคคลที่สามไม่สามารถทำลายรหัสของคุณได้ และคุณไม่จำเป็นต้องเชื่อมโยงไปยังไลบรารีการจัดการหน่วยความจำที่เฉพาะเจาะจง) แต่เช่นเดียวกับรหัสทั้งหมดขึ้นอยู่กับคุณว่าคุณชอบอะไรที่สุด ด้วยเหตุนี้คนส่วนใหญ่จึงเลือกใช้ตัวอย่างที่ 1 จนกว่าพวกเขาจะถูกไฟไหม้หลายครั้งจนไม่ยอมเขียนแบบนั้นอีกต่อไป;)
คำเตือน:
ฉันเลิกใช้ไปหลายปีแล้วและตอนนี้ C ของฉันค่อนข้างเป็นสนิม โค้ดสาธิตนี้ควรคอมไพล์อย่างถูกต้องด้วย C (ใช้ได้สำหรับคอมไพเลอร์ C ++)