const char * const so với const char *?


110

Tôi đang chạy qua một số chương trình ví dụ để làm quen lại với C ++ và tôi đã gặp câu hỏi sau. Đầu tiên, đây là mã ví dụ:

void print_string(const char * the_string)
{
    cout << the_string << endl;
}

int main () {
    print_string("What's up?");
}

Trong đoạn mã trên, tham số cho print_string thay vào đó có thể là const char * const the_string. Điều nào sẽ đúng hơn cho điều này?

Tôi hiểu rằng sự khác biệt là một cái là con trỏ đến một ký tự không đổi, trong khi cái kia là một con trỏ đến một ký tự không đổi. Nhưng tại sao cả hai đều hoạt động? Khi nào nó sẽ có liên quan?

Câu trả lời:


244

Cái sau ngăn bạn sửa đổi the_stringbên trong print_string. Nó thực sự sẽ phù hợp ở đây, nhưng có lẽ sự dài dòng đã khiến nhà phát triển phải kinh ngạc.

char* the_string: Tôi có thể thay đổi charthe_stringđiểm, và tôi có thể thay đổi charmà nó trỏ.

const char* the_string: Tôi có thể thay đổi charthe_stringđiểm, nhưng tôi không thể thay đổi charmà nó trỏ.

char* const the_string: Tôi không thể thay đổi charthe_stringđiểm, nhưng tôi có thể thay đổi charmà nó trỏ.

const char* const the_string: Tôi không thể thay đổi charthe_stringđiểm, cũng không phải tôi có thể thay đổi charmà nó trỏ.


11
+1 cho câu cuối cùng. Hằng đúng là dài dòng, nhưng rất đáng giá.
mskfisher

6
@Xeo: biểu mẫu của bạn thậm chí còn khó hiểu hơn bởi vì nó chỉ là một chuyển vị để thay đổi hoàn toàn ý nghĩa của nó. const char *tốt hơn nhiều vì constnó ở phía hoàn toàn trái ngược.
R .. GitHub DỪNG TRỢ GIÚP TỪ NGÀY

7
@R ..: Chà, ít nhất là đối với tôi thì không. Đọc từ phải sang trái, tôi nhận được "con trỏ đến const char". Đối với tôi, nó chỉ cảm thấy tốt hơn theo cách đó.
Xeo 09/02/11

6
Chà bạn đang tự đánh lừa mình vì các loại C được đọc từ trong ra ngoài, không phải từ trái sang phải. :-)
R .. GitHub DỪNG TRỢ GIÚP ICE

11
Tôi là một chút bối rối Tôi dường như chỉ có một người không hiểu được điều này ... nhưng sự khác biệt giữa "char là những gì để mà nó trỏ" và "char tại mà nó trỏ"?
Thiếu

138
  1. Con trỏ có thể thay đổi đến một ký tự có thể thay đổi

    char *p;
  2. Con trỏ có thể thay đổi đến một ký tự không đổi

    const char *p;
  3. Con trỏ liên tục đến một ký tự có thể thay đổi

    char * const p; 
  4. Con trỏ liên tục đến một ký tự không đổi

    const char * const p;

Điều này không nên: const char* p; --> constant pointer to mutable characterchar *const p; --> mutable pointer to constant character
PnotNP

2
@NulledPointer No. Khai báo C ++ được tạo từ phải sang trái. Do đó, bạn có thể đọc const char * plà: "p là một con trỏ đến một hằng số ký tự" hoặc một con trỏ có thể thay đổi đến một ký tự hằng như James đã tuyên bố một cách chính xác. tương tự với thứ hai :).
Samidamaru

28

const char * constphương tiện con trỏ cũng như các dữ liệu con trỏ chỉ vào, là cả const!

const char *có nghĩa là chỉ dữ liệu mà con trỏ trỏ tới, là const. Tuy nhiên, bản thân con trỏ không phải là const.

Thí dụ.

const char *p = "Nawaz";
p[2] = 'S'; //error, changing the const data!
p="Sarfaraz"; //okay, changing the non-const pointer. 

const char * const p = "Nawaz";
p[2] = 'S'; //error, changing the const data!
p="Sarfaraz"; //error, changing the const pointer. 

20

(Tôi biết điều này đã cũ nhưng dù sao thì tôi cũng muốn chia sẻ.)

Chỉ muốn giải thích thêm về câu trả lời của Thomas Matthews. Quy tắc Phải-Trái của khai báo loại C nói khá nhiều: khi đọc khai báo loại C, hãy bắt đầu từ mã định danh và đi sang phải khi bạn có thể và sang trái khi bạn không thể.

Điều này được giải thích tốt nhất với một vài ví dụ:

ví dụ 1

  • Bắt đầu từ mã định danh, chúng tôi không thể đi sang phải nên chúng tôi đi sang trái

    const char* const foo
                ^^^^^

    foo là một hằng số ...

  • Tiếp tục sang trái

    const char* const foo
              ^

    foo là một con trỏ liên tục đến ...

  • Tiếp tục sang trái

    const char* const foo
          ^^^^

    foo là một con trỏ hằng đến char ...

  • Tiếp tục sang trái

    const char* const foo
    ^^^^^

    foo là một con trỏ hằng đến hằng số char (Hoàn thành!)

Ví dụ 2

  • Bắt đầu từ mã định danh, chúng tôi không thể đi sang phải nên chúng tôi đi sang trái

    char* const foo
          ^^^^^

    foo là một hằng số ...

  • Tiếp tục sang trái

    char* const foo
        ^

    foo là một con trỏ liên tục đến ...

  • Tiếp tục sang trái

    char* const foo
    ^^^^

    foo là một con trỏ hằng đến char (Hoàn thành!)

Ví dụ 1337

  • Bắt đầu từ mã định danh, nhưng bây giờ chúng ta có thể đi đúng!

    const char* const* (*foo[8])()
                            ^^^

    foo là một mảng 8 ...

  • Nhấn vào dấu ngoặc để không thể sang phải nữa, hãy đi sang trái

    const char* const* (*foo[8])()
                        ^

    foo là một mảng 8 con trỏ tới ...

  • Đã hoàn thành bên trong dấu ngoặc đơn, bây giờ có thể chuyển sang bên phải

    const char* const* (*foo[8])()
                                ^^

    foo là một mảng gồm 8 con trỏ đến hàm trả về ...

  • Không có gì bên phải, đi bên trái

    const char* const* (*foo[8])()
                     ^

    foo là một mảng gồm 8 con trỏ đến hàm trả về một con trỏ đến ...

  • Tiếp tục sang trái

    const char* const* (*foo[8])()
                ^^^^^

    foo là một mảng gồm 8 con trỏ đến các hàm trả về một con trỏ đến một hằng số ...

  • Tiếp tục sang trái

    const char* const* (*foo[8])()
              ^

    foo là một mảng gồm 8 con trỏ đến các hàm trả về một con trỏ đến một con trỏ hằng đến một ...

  • Tiếp tục sang trái

    const char* const* (*foo[8])()
          ^^^^

    foo là một mảng gồm 8 con trỏ đến các hàm trả về một con trỏ đến một con trỏ hằng đến một char ...

  • Tiếp tục sang trái

    const char* const* (*foo[8])()
    ^^^^^

    foo là một mảng gồm 8 con trỏ đến các hàm trả về một con trỏ đến một con trỏ hằng đến một hằng số char (Hoàn thành!)

Giải thích thêm: http://www.unixwiz.net/techtips/reading-cdecl.html


CMIIW, const char * const foo nên tương đương với char const * const foo?
luochenhuan

@luochenhuan Vâng, đúng là như vậy.
Garrett

12

Nhiều người đề nghị đọc thông số loại từ phải sang trái.

const char * // Pointer to a `char` that is constant, it can't be changed.
const char * const // A const pointer to const data.

Trong cả hai dạng, con trỏ trỏ đến dữ liệu không đổi hoặc chỉ đọc.

Ở dạng thứ hai, con trỏ không thể thay đổi được; con trỏ sẽ luôn trỏ đến cùng một nơi.


3

Sự khác biệt là mà không cần bổ sung const, lập trình viên có thể thay đổi bên trong phương thức, nơi con trỏ trỏ đến; ví dụ:

 void print_string(const char * the_string)
 {
    cout << the_string << endl;
    //....
    the_string = another_string();
    //....

 }

Thay vào đó, điều đó sẽ là bất hợp pháp nếu chữ ký void print_string(const char * const the_string)

Nhiều lập trình viên cảm thấy quá dài dòng (trong hầu hết các trường hợp) consttừ khóa phụ và bỏ qua nó, mặc dù nó sẽ đúng về mặt ngữ nghĩa.


2

Trong phần sau, bạn đảm bảo không sửa đổi cả con trỏ và ký tự trong phần đầu tiên, bạn chỉ đảm bảo rằng nội dung sẽ không thay đổi nhưng bạn có thể di chuyển con trỏ xung quanh


Ahh vậy nếu không có hằng số cuối cùng, tôi thực sự có thể đặt con trỏ trỏ đến một chuỗi hoàn toàn khác?
pict

Có, nếu không có hằng số cuối cùng đó, bạn có thể sử dụng con trỏ tham số để thực hiện một số lần lặp theo số học con trỏ, trong đó nếu có hằng số đó, bạn phải tạo con trỏ của riêng mình, bản sao của tham số đó.
Jesus Ramos

2

Không có lý do tại sao một trong hai không hoạt động. Tất cảprint_string() làm là in giá trị. Nó không cố gắng sửa đổi nó.

Bạn nên tạo một hàm không sửa đổi các đối số đánh dấu dưới dạng const. Ưu điểm là các biến không thể thay đổi (hoặc bạn không muốn thay đổi) có thể được chuyển cho các hàm này mà không bị lỗi.

Về cú pháp chính xác, bạn muốn chỉ ra loại đối số nào là "an toàn" để được truyền cho hàm.


2

Tôi nghĩ rằng nó khác nhau hiếm khi có liên quan, vì hàm của bạn không được gọi với các đối số như & * the_string hoặc ** the_string. Bản thân con trỏ là một đối số kiểu giá trị, vì vậy ngay cả khi bạn sửa đổi nó, bạn sẽ không thay đổi bản sao được sử dụng để gọi hàm của bạn. Phiên bản bạn đang hiển thị đảm bảo rằng chuỗi sẽ không thay đổi và tôi nghĩ điều đó là đủ trong trường hợp này.


2

const char *nghĩa là bạn không thể sử dụng con trỏ để thay đổi những gì được trỏ tới. Tuy nhiên, bạn có thể thay đổi con trỏ để trỏ đến một thứ khác.

Xem xét:

const char * promptTextWithDefault(const char * text)
{
    if ((text == NULL) || (*text == '\0'))
        text = "C>";
    return text;
}

Tham số là một con trỏ không phải const tới const char, vì vậy nó có thể được thay đổi thành một const char *giá trị khác (như một chuỗi hằng số). Tuy nhiên, nếu chúng tôi viết nhầm*text = '\0' thì chúng tôi sẽ gặp lỗi biên dịch.

Có thể cho rằng, nếu bạn không có ý định thay đổi thông số đang trỏ đến, bạn có thể tạo tham số const char * const text, nhưng việc làm như vậy không phổ biến. Chúng tôi thường cho phép các hàm thay đổi giá trị được truyền cho tham số (vì chúng tôi truyền tham số theo giá trị, bất kỳ thay đổi nào không ảnh hưởng đến trình gọi).

BTW: Nên tránh char const *vì nó thường bị đọc sai - nó có nghĩa giống như const char *, nhưng quá nhiều người đọc nó thành nghĩa char * const.


Chà! Tôi đang cố gắng tìm ra sự khác biệt giữa const char *chữ ký của tôi và chữ ký char const *- cách bạn nói BTW của bạn thực sự hữu ích!
sage

1

Gần như tất cả các câu trả lời khác đều đúng, nhưng chúng bỏ sót một khía cạnh của điều này: Khi bạn sử dụng phần bổ sung constcho một tham số trong khai báo hàm, về cơ bản trình biên dịch sẽ bỏ qua nó. Trong giây lát, hãy bỏ qua sự phức tạp của ví dụ của bạn là một con trỏ và chỉ sử dụng một int.

void foo(const int x);

khai báo chức năng tương tự như

void foo(int x);

Chỉ trong định nghĩa của hàm là constý nghĩa bổ sung :

void foo(const int x) {
    // do something with x here, but you cannot change it
}

Định nghĩa này tương thích với một trong các khai báo ở trên. Người gọi không quan tâm đó xconst chi tiết triển khai không liên quan tại trang web cuộc gọi.

Nếu bạn có một constcon trỏ tới constdữ liệu, các quy tắc tương tự sẽ được áp dụng:

// these declarations are equivalent
void print_string(const char * const the_string);
void print_string(const char * the_string);

// In this definition, you cannot change the value of the pointer within the
// body of the function.  It's essentially a const local variable.
void print_string(const char * const the_string) {
    cout << the_string << endl;
    the_string = nullptr;  // COMPILER ERROR HERE
}

// In this definition, you can change the value of the pointer (but you 
// still can't change the data it's pointed to).  And even if you change
// the_string, that has no effect outside this function.
void print_string(const char * the_string) {
    cout << the_string << endl;
    the_string = nullptr;  // OK, but not observable outside this func
}

Rất ít lập trình viên C ++ bận tâm đến việc tạo các tham số const, ngay cả khi chúng có thể là như vậy, bất kể các tham số đó có phải là con trỏ hay không.


"Trình biên dịch về cơ bản sẽ bỏ qua nó" không phải lúc nào cũng đúng, Visual C ++ 2015 sẽ đưa ra cảnh báo nếu bạn thêm phần bổ sung constvào tham số hàm của mình trong định nghĩa nhưng không khai báo.
raymai97

@ raymai97: Tôi nghĩ đó là một lỗi trong trình biên dịch năm 2015, nhưng tôi không có bản 2015 để kiểm tra. Tôi làm việc như đã mô tả vào năm 2017, theo các cuộc trò chuyện của tôi với một số chuyên gia về tiêu chuẩn, đó là hành vi được mong đợi.
Adrian McCarthy

-1

Sự khác biệt giữa cả hai là char * có thể trỏ đến bất kỳ con trỏ tùy ý nào. Ngược lại, Const char *, trỏ đến các hằng số được xác định trong phần DATA của tệp thực thi. Và như vậy, bạn không thể sửa đổi các giá trị ký tự của chuỗi const char *.


Tôi không hỏi sự khác biệt giữa char * và const char *. Tôi đang yêu cầu giữa const char * và const char * const
pict

Câu trả lời này không chính xác. A const char*có thể chỉ bất cứ nơi nào nó muốn.
Khối
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.