Trả về một chuỗi C từ một hàm


109

Tôi đang cố gắng trả về một chuỗi C từ một hàm, nhưng nó không hoạt động. Đây là mã của tôi.

char myFunction()
{
    return "My String";
}

Trong maintôi đang gọi nó như thế này:

int main()
{
  printf("%s", myFunction());
}

Tôi cũng đã thử một số cách khác myFunction, nhưng chúng không hiệu quả. Ví dụ:

char myFunction()
{
  char array[] = "my string";
  return array;
}

Lưu ý: Tôi không được phép sử dụng con trỏ!

Thông tin cơ bản về vấn đề này:

Có chức năng tìm ra đó là tháng nào. Ví dụ: nếu là 1 thì nó trả về tháng 1, v.v.

Vì vậy, khi nó đang diễn ra để in, nó làm nó như thế này: printf("Month: %s",calculateMonth(month));. Bây giờ vấn đề là làm thế nào để trả về chuỗi đó từ calculateMonthhàm.


10
Thật không may, bạn cần con trỏ trong trường hợp này.
Nick Bedford

1
@Hayato Vâng tôi tin rằng chúng tôi là người lớn ở đây và biết nó sẽ trả về 0, đó chỉ là vì lợi ích của cho ví dụ lox ..
itsaboutcode

3
return 0được ngụ ý theo mặc định chỉ trong C99 (và C ++) nhưng không phải trong C90.
hrnt 30/09/09

1
Sau đó, bạn sẽ không thể làm điều đó, bên cạnh các hack ngu ngốc mà dù sao thì một thao tác con trỏ thực sự chỉ được chia nhỏ. Con trỏ tồn tại vì một lý do ...: |
GManNickG 30/09/09

Câu trả lời:


222

Chữ ký hàm của bạn cần phải là:

const char * myFunction()
{
    return "My String";
}

Lý lịch:

Nó rất cơ bản đối với C & C ++, nhưng cần thảo luận thêm một chút.

Trong C (& C ++ cho vấn đề đó), một chuỗi chỉ là một mảng các byte được kết thúc bằng byte 0 - do đó thuật ngữ "string-zero" được sử dụng để biểu thị hương vị cụ thể này của chuỗi. Có những loại chuỗi khác, nhưng trong C (& C ++), hương vị này vốn dĩ được ngôn ngữ tự hiểu. Các ngôn ngữ khác (Java, Pascal, v.v.) sử dụng các phương pháp luận khác nhau để hiểu "chuỗi của tôi".

Nếu bạn đã từng sử dụng Windows API (bằng C ++), bạn sẽ thấy các tham số hàm khá thường xuyên như: "LPCSTR lpszName". Phần 'sz' đại diện cho khái niệm 'chuỗi-không': một mảng các byte với dấu chấm hết (/ không) null.

Làm rõ:

Vì lợi ích của 'phần giới thiệu' này, tôi sử dụng từ 'byte' và 'ký tự' thay thế cho nhau, vì nó dễ học hơn theo cách này. Lưu ý rằng có các phương pháp khác (hệ thống ký tự rộng và nhiều byte ( mbcs )) được sử dụng để xử lý các ký tự quốc tế. UTF-8 là một ví dụ về mbcs. Vì lợi ích của phần giới thiệu, tôi lặng lẽ 'bỏ qua' tất cả những điều này.

Ký ức:

Điều này có nghĩa là một chuỗi như "chuỗi của tôi" thực sự sử dụng 9 + 1 (= 10!) Byte. Điều quan trọng là bạn phải biết khi nào thì cuối cùng bạn cũng có thể phân bổ chuỗi động.

Vì vậy, nếu không có 'số không kết thúc' này, bạn không có một chuỗi. Bạn có một mảng ký tự (còn gọi là bộ đệm) quanh quẩn trong bộ nhớ.

Tuổi thọ của dữ liệu:

Việc sử dụng hàm theo cách này:

const char * myFunction()
{
    return "My String";
}

int main()
{
    const char* szSomeString = myFunction(); // Fraught with problems
    printf("%s", szSomeString);
}

... thường sẽ khiến bạn gặp phải các lỗi ngẫu nhiên không có ngoại lệ / phân đoạn và tương tự, đặc biệt là 'xuống đường'.

Tóm lại, mặc dù câu trả lời của tôi là đúng - 9 lần trong số 10 bạn sẽ kết thúc với một chương trình bị treo nếu bạn sử dụng theo cách đó, đặc biệt nếu bạn nghĩ rằng nên làm theo cách đó. Tóm lại: Nói chung là không.

Ví dụ, hãy tưởng tượng một lúc nào đó trong tương lai, chuỗi bây giờ cần được thao tác theo một cách nào đó. Nói chung, một lập trình viên sẽ 'đi theo con đường dễ dàng' và (cố gắng) viết mã như thế này:

const char * myFunction(const char* name)
{
    char szBuffer[255];
    snprintf(szBuffer, sizeof(szBuffer), "Hi %s", name);
    return szBuffer;
}

Đó là, chương trình của bạn sẽ sụp đổ vì trình biên dịch (có thể / không) đã phát hành bộ nhớ được sử dụng bởi szBufferdo thời gian printf()trong main()được gọi. (Trình biên dịch của bạn cũng nên cảnh báo trước cho bạn về những vấn đề như vậy.)

Có hai cách để trả về các chuỗi không quá dễ dàng.

  1. trả về bộ đệm (tĩnh hoặc phân bổ động) tồn tại trong một thời gian. Trong C ++ sử dụng 'các lớp trợ giúp' (ví dụ std::string:) để xử lý tuổi thọ của dữ liệu (yêu cầu thay đổi giá trị trả về của hàm) hoặc
  2. truyền một bộ đệm cho hàm được điền thông tin.

Lưu ý rằng không thể sử dụng chuỗi mà không sử dụng con trỏ trong C. Như tôi đã trình bày, chúng đồng nghĩa. Ngay cả trong C ++ với các lớp mẫu, luôn có các bộ đệm (nghĩa là con trỏ) được sử dụng trong nền.

Vì vậy, để trả lời tốt hơn (câu hỏi hiện đã được sửa đổi). (Chắc chắn sẽ có nhiều 'câu trả lời khác' có thể được cung cấp.)

Câu trả lời An toàn hơn:

Ví dụ 1, sử dụng các chuỗi được cấp phát tĩnh:

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"
}

Điều mà 'static' làm ở đây (nhiều lập trình viên không thích kiểu 'cấp phát' này) là các chuỗi được đưa vào phân đoạn dữ liệu của chương trình. Đó là, nó được phân bổ vĩnh viễn.

Nếu bạn chuyển sang C ++, bạn sẽ sử dụng các chiến lược tương tự:

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;
    }
}

... nhưng có lẽ sẽ dễ dàng hơn khi sử dụng các lớp trợ giúp, chẳng hạn như std::stringnếu bạn đang viết mã để sử dụng cho riêng mình (và không phải là một phần của thư viện để chia sẻ với người khác).

Ví dụ 2, sử dụng bộ đệm do người gọi xác định:

Đây là cách dễ dàng hơn để truyền các chuỗi xung quanh. Dữ liệu trả về không bị bên gọi thao túng. Đó là, ví dụ 1 có thể dễ dàng bị một bên gọi điện lạm dụng và khiến bạn gặp phải các lỗi ứng dụng. Bằng cách này, nó an toàn hơn nhiều (mặc dù sử dụng nhiều dòng mã hơn):

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"
}

Có rất nhiều lý do tại sao phương pháp thứ hai lại tốt hơn, đặc biệt nếu bạn đang viết một thư viện để người khác sử dụng (bạn không cần phải khóa vào một lược đồ phân bổ / thỏa thuận cụ thể, các bên thứ ba không thể phá mã của bạn, và bạn không cần phải liên kết đến một thư viện quản lý bộ nhớ cụ thể), nhưng giống như tất cả các mã, tùy thuộc vào bạn về những gì bạn thích nhất. Vì lý do đó, hầu hết mọi người chọn ví dụ 1 cho đến khi họ bị cháy nhiều lần đến mức họ từ chối viết theo cách đó nữa;)

Tuyên bố từ chối trách nhiệm:

Tôi đã nghỉ hưu vài năm trước và giờ C của tôi hơi bị gỉ. Tất cả mã demo này phải được biên dịch đúng cách với C (mặc dù vậy, nó cũng được cho bất kỳ trình biên dịch C ++ nào).


2
Trên thực tế, hàm cần trả về a char *, vì các chuỗi ký tự trong C thuộc loại char[]. Tuy nhiên, chúng không được sửa đổi theo bất kỳ cách nào, vì vậy việc trả lại const char*được ưu tiên hơn (xem securecoding.cert.org/confluence/x/mwAV ). Việc trả về char *có thể cần thiết nếu chuỗi sẽ được sử dụng trong một hàm thư viện kế thừa hoặc bên ngoài mà (không may là) yêu cầu một char*đối số là đối số, thậm chí khó nó sẽ chỉ đọc từ nó. Mặt khác, C ++ có kiểu chuỗi ký tự const char[](và kể từ C ++ 11, bạn cũng có thể có chuỗi ký std::stringtự).
TManhente

17
@cmroanirgo tiền tố của tôi tuyên bố với người đọc rằng hàm được tạo bởi người dùng. Tôi thấy nó hoàn toàn hợp lý khi sử dụng trong bối cảnh như vậy.
lượng

4
theo đây: stackoverflow.com/questions/9970295/… , bạn có thể trả về chuỗi theo nghĩa đen
giorgim 22/10/15

6
Mã được đánh dấu fraught with problemstrong phần "Tuổi thọ của dữ liệu" thực sự hoàn toàn hợp lệ. Chuỗi ký tự có thời gian tồn tại tĩnh trong C / C ++. Xem liên kết mà Giorgi đề cập ở trên.
chengiz

1
@cmroanirgo Trả lại các ký tự chuỗi là một phương pháp hay và phong cách tốt. Nó không "đầy vấn đề", và nó sẽ không bị hỏng 9/10 lần: Nó sẽ không bao giờ bị hỏng. Ngay cả các trình biên dịch từ những năm 80 (ít nhất là những trình tôi đã sử dụng) hỗ trợ chính xác tuổi thọ không giới hạn của các ký tự chuỗi. Lưu ý: Tôi không chắc ý của bạn về việc chỉnh sửa câu trả lời: Tôi vẫn thấy nó cho biết nó dễ bị lỗi.
cesss

12

Chuỗi AC được định nghĩa là một con trỏ tới một mảng ký tự.

Nếu bạn không thể có con trỏ, theo định nghĩa, bạn không thể có chuỗi.


Bạn có thể vượt qua trong một mảng tới một hàm và sau đó hoạt động trên mảng: void foo( char array[], int length). Tất nhiên, arraynó là một con trỏ dưới mui xe, nhưng nó không phải là một con trỏ "rõ ràng", và do đó nó có thể trực quan hơn đối với một số người đang học mảng nhưng chưa hiểu rõ về con trỏ.
jvriesem

12

Lưu ý chức năng mới này:

const char* myFunction()
{
    static char array[] = "my string";
    return array;
}

Tôi đã định nghĩa "mảng" là tĩnh. Ngược lại, khi hàm kết thúc, biến (và con trỏ bạn đang trả về) sẽ ra khỏi phạm vi. Vì bộ nhớ đó được cấp phát trên ngăn xếp, và nó sẽ bị hỏng. Nhược điểm của việc triển khai này là mã không được nhập lại và không an toàn cho luồng.

Một sự thay thế khác sẽ là sử dụng malloc để phân bổ chuỗi trong heap và sau đó giải phóng trên các vị trí chính xác của mã của bạn. Mã này sẽ được nhập lại và an toàn.

Như đã lưu ý trong nhận xét, đây là một thực tiễn rất xấu, vì kẻ tấn công sau đó có thể chèn mã vào ứng dụng của bạn (anh ta / cô ta cần mở mã bằng GDB, sau đó tạo điểm ngắt và sửa đổi giá trị của biến trả về để làm tràn và niềm vui chỉ bắt đầu).

Nên để người gọi xử lý về cấp phát bộ nhớ. Xem ví dụ mới này:

char* myFunction(char* output_str, size_t max_len)
{
   const char *str = "my string";
   size_t l = strlen(str);
   if (l+1 > max_len) {
      return NULL;
   }
   strcpy(str, str, l);
   return input;
}

Lưu ý rằng nội dung duy nhất có thể được sửa đổi là nội dung mà người dùng. Một tác dụng phụ khác - mã này hiện đang an toàn, ít nhất là theo quan điểm thư viện. Lập trình viên gọi phương thức này nên xác minh rằng phần bộ nhớ được sử dụng là an toàn luồng.


2
Đây thường là một cách tồi tệ để tiếp cận mọi thứ. Ký tự * có thể được thao tác bởi mã xung quanh. Đó là, bạn có thể làm những việc như sau: strcpy (myFunction (), "Một chuỗi thực sự dài"); và chương trình của bạn sẽ bị treo do vi phạm quyền truy cập.
cmroanirgo

Một cái gì đó bị thiếu gần "một trong những người dùng" .
Peter Mortensen

8

Vấn đề của bạn là với kiểu trả về của hàm - nó phải là:

char *myFunction()

... và sau đó công thức ban đầu của bạn sẽ hoạt động.

Lưu ý rằng bạn không thể có chuỗi C mà không có con trỏ liên quan, ở đâu đó dọc theo dòng.

Ngoài ra: Bật cảnh báo trình biên dịch của bạn. Nó nên đã cảnh báo bạn về việc dòng trả về chuyển đổi một char *thành charmà không có diễn viên rõ ràng.


1
Tôi nghĩ rằng chữ ký nên const char * vì chuỗi là một ký tự nhưng nếu tôi không nhầm thì trình biên dịch sẽ chấp nhận điều này.
Luke

5

Dựa trên câu hỏi cơ bản mới được thêm vào của bạn, tại sao không chỉ trả về một số nguyên từ 1 đến 12 cho tháng và để hàm main () sử dụng câu lệnh switch hoặc if-else bậc thang để quyết định nội dung sẽ in? Đó chắc chắn không phải là cách tốt nhất - char * sẽ là - nhưng trong bối cảnh của một lớp học như thế này, tôi tưởng tượng nó có lẽ là cách thanh lịch nhất.


3

Bạn có thể tạo mảng trong trình gọi, là hàm chính và truyền mảng vào callee là myFunction () của bạn. Do đó, myFunction có thể điền chuỗi vào mảng. Tuy nhiên, bạn cần khai báo myFunction () là

char* myFunction(char * buf, int buf_len){
  strncpy(buf, "my string", buf_len);
  return buf;
}

Và trong hàm main, myFunction nên được gọi theo cách này:

char array[51];
memset(array, 0, 51); /* All bytes are set to '\0' */
printf("%s", myFunction(array, 50)); /* The buf_len argument  is 50, not 51. This is to make sure the string in buf is always null-terminated (array[50] is always '\0') */

Tuy nhiên, một con trỏ vẫn được sử dụng.


2

Kiểu trả về hàm của bạn là một ký tự đơn ( char). Bạn nên trả về một con trỏ đến phần tử đầu tiên của mảng ký tự. Nếu bạn không thể sử dụng con trỏ, thì bạn đã bị hỏng. :(


2

Thế còn cái này thì như thế nào:

void print_month(int month)
{
    switch (month)
    {
        case 0:
            printf("January");
            break;
        case 1:
            printf("february");
            break;
        ...etc...
    }
}

Và gọi đó với tháng bạn tính ở một nơi khác.


1
+1 không phải những gì OP yêu cầu nhưng đây có thể là những gì nhiệm vụ mong đợi bạn làm, vì anh ấy không thể sử dụng con trỏ.
Vitim.us

Ngay cả printf cũng sử dụng con trỏ. Một con trỏ giống như một con dao - rất cần thiết cho cuộc sống và làm việc, nhưng bạn phải cầm nó bằng tay cầm và dùng mặt sắc để cắt nếu không bạn sẽ gặp khó khăn. Việc đặt dấu cách không may trong định nghĩa hàm là một lỗi não đối với nhiều lập trình viên C mới. char * func (char * s); char func (char * s); char func * char * s); đều giống nhau nhưng tất cả đều trông khác nhau và gây nhầm lẫn phức tạp, * cũng là toán tử khử tham chiếu cho các biến là con trỏ.
Chris Reid,

1

A charchỉ là một ký tự một byte. Nó không thể lưu trữ chuỗi ký tự, cũng không phải là một con trỏ (mà bạn dường như không thể có). Do đó, bạn không thể giải quyết vấn đề của mình mà không sử dụng con trỏ ( char[]là đường cú pháp cho).


1

Nếu bạn thực sự không thể sử dụng con trỏ, hãy làm như sau:

char get_string_char(int index)
{
    static char array[] = "my string";
    return array[index];
}

int main()
{
    for (int i = 0; i < 9; ++i)
        printf("%c", get_string_char(i));
    printf("\n");
    return 0;
}

Con số 9 ma thuật thật khủng khiếp, và đây không phải là một ví dụ về lập trình tốt. Nhưng bạn sẽ có được điểm. Lưu ý rằng con trỏ và mảng giống nhau (loại), vì vậy điều này hơi gian lận.


Thông thường, nếu bạn cần thực hiện các giải pháp như vậy cho các vấn đề ở nhà, thì các giả định ban đầu của bạn là sai.
hrnt 30/09/09

1

Chà, trong mã của bạn, bạn đang cố gắng trả về một String(trong C không là gì ngoài một mảng ký tự kết thúc bằng null), nhưng kiểu trả về của hàm charđang gây ra mọi rắc rối cho bạn. Thay vào đó, bạn nên viết nó theo cách này:

const char* myFunction()
{

    return "My String";

}

Và luôn luôn tốt để đủ điều kiện cho kiểu của bạn consttrong khi gán các ký tự trong C cho các con trỏ vì các ký tự trong C không thể sửa đổi được.


0

Nguyên mẫu hàm của bạn cho biết hàm của bạn sẽ trả về một ký tự. Do đó, bạn không thể trả về một chuỗi trong hàm của mình.



0

Trả về chuỗi từ hàm

#include <stdio.h>

const char* greet() {
  return "Hello";
}

int main(void) {
  printf("%s", greet());
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.