Const trước hay const sau?


145

Để bắt đầu, bạn có thể biết rằng constcó thể được sử dụng để làm cho dữ liệu của đối tượng hoặc con trỏ không thể sửa đổi hoặc cả hai.

const Object* obj; // can't change data
Object* const obj; // can't change pointer
const Object* const obj; // can't change data or pointer

Tuy nhiên, bạn cũng có thể sử dụng cú pháp:

Object const *obj; // same as const Object* obj;

Điều duy nhất có vẻ quan trọng là phía nào của dấu hoa thị bạn đặt consttừ khóa. Cá nhân tôi thích đặt constbên trái của loại để chỉ định dữ liệu của nó không thể sửa đổi vì tôi thấy nó đọc tốt hơn trong suy nghĩ từ trái sang phải của tôi nhưng cú pháp nào đến trước?

Quan trọng hơn tại sao có hai cách chính xác để xác định constdữ liệu và trong trường hợp nào bạn muốn hoặc cần cái này hơn cái kia nếu có?

Biên tập:

Vì vậy, có vẻ như đây là một quyết định tùy tiện khi tiêu chuẩn về cách trình biên dịch diễn giải mọi thứ đã được soạn thảo từ lâu trước khi tôi được sinh ra. Vì constđược áp dụng cho những gì ở bên trái của từ khóa (theo mặc định?) Tôi đoán rằng họ cho rằng không có hại gì khi thêm "phím tắt" để áp dụng từ khóa và nhập vòng loại theo những cách khác ít nhất là cho đến khi thời điểm khai báo thay đổi. phân tích cú pháp * hoặc & ...

Đây cũng là trường hợp của C?


9
Trong các macro luôn luôn thêm const sau loại, ví dụ #define MAKE_CONST(T) T constthay vì #define MAKE_CONST(T) const Tvậy MAKE_CONST(int *)sẽ mở rộng chính xác int * constthay vì const int *.
jotik

6
Tôi đã thấy hai phong cách này được gọi là "đông const" và "tây const".
Tom Anderson

13
@TomAnderson nhưng thực sự nó phải là "đông const" và "const west".
YSC

Câu trả lời:


96

Tại sao có hai cách chính xác để chỉ định constdữ liệu và trong trường hợp nào bạn muốn hoặc cần cái này hơn cái kia nếu có?

Về cơ bản, lý do mà vị trí của các nhà đầu cơ consttrước dấu hoa thị không quan trọng là ngữ pháp C đã được Kernighan và Ritchie định nghĩa theo cách đó.

Lý do họ xác định ngữ pháp theo cách này có khả năng là trình biên dịch C của họ đã phân tích cú pháp đầu vào từ trái sang phải và xử lý xong từng mã thông báo khi nó tiêu thụ. Việc sử dụng *mã thông báo thay đổi trạng thái của khai báo hiện tại thành một loại con trỏ. Gặp constsau *nghĩa là constvòng loại được áp dụng cho khai báo con trỏ; gặp nó trước khi *có nghĩa là vòng loại được áp dụng cho dữ liệu được trỏ đến.

Bởi vì ý nghĩa ngữ nghĩa không thay đổi nếu constvòng loại xuất hiện trước hoặc sau các chỉ định loại, nên nó được chấp nhận theo một trong hai cách.

Một loại trường hợp tương tự phát sinh khi khai báo các con trỏ hàm, trong đó:

  • void * function1(void)khai báo một chức năng mà trở về void *,

  • void (* function2)(void)khai báo một con trỏ hàm đến một hàm trả về void.

Một lần nữa, điều cần chú ý là cú pháp ngôn ngữ hỗ trợ trình phân tích cú pháp từ trái sang phải.


7
Kernighan là đồng tác giả của cuốn sách nhưng không tham gia vào thiết kế của C, chỉ là Ritchie.
Tom Zych

8
Tôi không bao giờ có thể nhớ cái nào là cái nào. Nhờ lời giải thích của bạn, cuối cùng tôi cũng có kỹ thuật để nhớ nó. Cảm ơn! Trước khi *trình phân tích cú pháp trình biên dịch không biết nó là con trỏ, do đó, nó là const cho giá trị dữ liệu. Sau * nó liên quan đến con trỏ không đổi. Xuất sắc. Và cuối cùng nó giải thích tại sao tôi có thể làm const chartốt như vậy char const.
Vit Bernatik

2
Việc đoán tại sao nó được thực hiện theo cách này có vẻ khá yếu / tự mâu thuẫn với tôi. Đó là, nếu tôi đang xác định một ngôn ngữ và viết một trình biên dịch, và tôi muốn giữ nó đơn giản và "phân tích cú pháp từ trái sang phải và xử lý xong mỗi mã thông báo khi nó tiêu thụ nó", như bạn nói, nó dường như đối với tôi Tôi sẽ yêu cầu constluôn luôn đến sau khi nó đủ điều kiện ... chính xác để tôi luôn có thể hoàn thành việc xử lý const ngay sau khi tôi tiêu thụ nó. Vì vậy, đây dường như là một lập luận cho việc cấm tây-const, hơn là cho phép nó.
Don nở

3
"Bởi vì ý nghĩa ngữ nghĩa không thay đổi nếu vòng loại const xuất hiện trước hoặc sau các chỉ định loại, nên nó được chấp nhận theo bất kỳ cách nào." Đó không phải là lý luận tròn? Câu hỏi là tại sao ý nghĩa ngữ nghĩa được định nghĩa như vậy, vì vậy tôi không nghĩ câu này đóng góp bất cứ điều gì.
Don nở

1
@donhatch Bạn phải nhớ rằng, liên quan đến ngày nay và các giả định chúng tôi đưa ra dựa trên sự quen thuộc với thiết kế ngôn ngữ lập trình tốt, ngôn ngữ là những điều khá mới mẻ hồi đó. Ngoài ra, cho dù một người có một ngôn ngữ cho phép hoặc bị hạn chế là một đánh giá giá trị. Ví dụ, python có nên có một ++toán tử? "Câu", imho, đã giúp tôi nhận ra không có lý do cụ thể nào khác ngoài lý do họ có thể. Có lẽ họ sẽ đưa ra một lựa chọn khác ngày hôm nay / có thể không.
ragerdl

75

Quy tắc là:

const áp dụng cho những gì còn lại của nó. Nếu không có gì ở bên trái thì nó áp dụng cho thứ bên phải của nó.

Tôi thích sử dụng const ở bên phải của vật thể chỉ vì nó là cách "gốc" const được định nghĩa.

Nhưng tôi nghĩ rằng đây là một quan điểm rất chủ quan.


18
Tôi thích đặt nó bên trái, nhưng tôi nghĩ đặt nó bên phải có ý nghĩa hơn. Bạn thường đọc các loại trong C ++ từ phải sang trái, ví dụ như Object const *là một con trỏ tới một đối tượng const. Nếu bạn đặt constbên trái, nó sẽ đọc như một con trỏ tới một đối tượng là const, nó không thực sự chảy rất tốt.
Collin Dauphinee

1
Tôi có ấn tượng rằng bên trái là sự nhất quán theo kiểu con người với các loại khai báo C khác (thông minh trên máy tính, nó không đúng vì constkhông phải là một lớp lưu trữ, nhưng mọi người không phải là trình phân tích cú pháp).
geekizard

1
@ Theo tôi tin rằng đó là một hướng dẫn hơn là một quy tắc và tôi thường nghe nó như một cách ghi nhớ cách trình biên dịch sẽ diễn giải nó ... Tôi hiểu cách nó hoạt động nên tôi chỉ tò mò về quá trình suy nghĩ đằng sau quyết định hỗ trợ cả hai cách.
AJG85

3
@HeathHunnicutt quy tắc tồn tại, nhưng nó chỉ phức tạp hơn một chút: c-faq.com/decl/spirus.anderson.html
imallett

2
@HeathHunnicutt: quy tắc xoắn ốc là phiên bản mở rộng của nhận xét của người bình luận đầu tiên "Bạn thường đọc các loại trong [C /] C ++ từ phải sang trái". Tôi đoán bạn đã mâu thuẫn với điều này. Tuy nhiên, tôi nghĩ rằng thay vào đó bạn có thể đã tham khảo câu trả lời.
imallett

56

Tôi thích cú pháp thứ hai. Nó giúp tôi theo dõi 'cái gì' không đổi bằng cách đọc khai báo kiểu từ phải sang trái:

Object * const obj;        // read right-to-left:  const pointer to Object
Object const * obj;        // read right-to-left:  pointer to const Object
Object const * const obj;  // read right-to-left:  const pointer to const Object

3
Chính xác. Một "con trỏ không đổi đến một đối tượng không đổi" là Object const* constkhông const const Object*. "const" không thể ở bên trái, trừ trường hợp đặc biệt có rất nhiều người yêu thích nó. (Xem Heath ở trên.)
cdunn2001

38

Thứ tự của các từ khóa trong một tuyên bố không phải là tất cả cố định. Có nhiều lựa chọn thay thế cho "một trật tự thực sự". Như thế này

int long const long unsigned volatile i = 0;

hoặc nó nên

volatile unsigned long long int const i = 0;

??


25
+1 cho một định nghĩa hoàn toàn khó hiểu về một biến đơn giản. :)
Xèo

@rubenvb - Vâng, thật không may là họ. Ngữ pháp chỉ nói rằng a decl-specifier-seqlà một chuỗi decl-specifiers. Không có thứ tự được đưa ra bởi ngữ pháp và số lần xuất hiện cho mỗi từ khóa chỉ bị giới hạn bởi một số quy tắc ngữ nghĩa (bạn có thể có một constnhưng hai long:-)
Bo Persson

3
@rubenvb - Có, unsignedlà một loại, giống như unsigned intint unsigned. unsigned longlà một loại khác, giống như unsigned long intint long unsigned. Xem mô hình?
Bo Persson

2
@Bo: Tôi thấy sự lộn xộn, phải có ba để xem một mô hình ;). OK, cảm ơn
rubenvb

1
Bạn đã từng có thể thêm staticvào mớ bòng bong của các từ, nhưng chỉ gần đây mới có trình biên dịch phàn nàn rằng staticcần phải đến trước.
Đánh dấu Lakata

8

Nguyên tắc đầu tiên là sử dụng bất kỳ định dạng nào mà tiêu chuẩn mã hóa địa phương của bạn yêu cầu. Sau đó: đặt constphía trước dẫn đến không có sự nhầm lẫn khi typedefs có liên quan, ví dụ:

typedef int* IntPtr;
const IntPtr p1;   // same as int* const p1;

Nếu tiêu chuẩn mã hóa của bạn cho phép typedef's con trỏ, thì nó thực sự nên nhấn mạnh vào việc đặt const sau loại. Trong mọi trường hợp nhưng khi áp dụng cho loại, const phải tuân theo những gì nó áp dụng, vì vậy sự gắn kết cũng tranh luận có lợi cho const sau. Nhưng hướng dẫn mã hóa địa phương thổi phồng tất cả những điều này; sự khác biệt thường không đủ quan trọng để quay lại và thay đổi tất cả các mã hiện có.


Tôi nghĩ rằng điều đó có thể làm nổi bật lý do tại sao chúng ta không có các loại con trỏ trong các tiêu chuẩn được xác định khá lỏng lẻo tại cửa hàng này.
AJG85

1
Chính sách của tôi (khi tôi quyết định một mình) là đặt const sau (vì sự gắn kết) không sử dụng typedefs cho con trỏ (hoặc nói chung là typedefs) :-). Và BTW, chuỗi :: iterator so với chuỗi :: const_iterator có lẽ cũng nên được đưa vào quyết định của bạn. (Chỉ để nhầm lẫn mọi thứ :-). Không có câu trả lời đúng.)
James Kanze

À vâng, tôi có thể bao gồm cả hành vi const std::string::const_iteratorcũng là biện pháp tốt;)
AJG85

2
@JamesKanze - Đợi một chút, giúp tôi ra khỏi đây ... Tôi không thấy sự nhầm lẫn trong ví dụ được đăng. Điều gì khác có const IntPtr p1thể có nghĩa là ngoài "con trỏ số nguyên không đổi" (nghĩa là "con trỏ không đổi đến số nguyên")? Không ai trong tâm trí của họ, ngay cả khi không biết làm thế nào IntPtrđược xác định, sẽ nghĩ rằng đó p1là có thể thay đổi. Và đối với vấn đề đó, tại sao bất cứ ai sẽ cho rằng đó *p1là bất biến? Hơn nữa, đặt const ở bất cứ nơi nào khác (ví dụ, IntPtr const p1), không thay đổi ngữ nghĩa gì cả.
Todd Lehman

3
@ToddLehman Bạn có thể không thấy sự nhầm lẫn, nhưng hầu hết các lập trình viên C ++ đều làm và hiểu sai một cách có hệ thống (không nghi ngờ gì về những điều như std::vector<T>::const_iterator, nơi nó không phải là trình lặp, nhưng nó đang chỉ vào điều gì).
James Kanze

7

Có những lý do lịch sử mà trái hoặc phải được chấp nhận. Stroustrup đã thêm const vào C ++ vào năm 1983 , nhưng nó đã không được đưa vào C cho đến C89 / C90.

Trong C ++, có một lý do chính đáng để luôn sử dụng const ở bên phải. Bạn sẽ nhất quán ở mọi nơi vì các hàm thành viên const phải được khai báo theo cách này:

int getInt() const;

1
... tốt, "lý do chính đáng" không thuyết phục lắm, bởi vì các vị trí có thể khác cho "const" sẽ không có nghĩa tương tự. const int& getInt(); int& const getInt();
Maestro

1
@Maestro: Tôi đang đề xuất int const& getInt();là tốt hơn so với tương đương const int& getInt();trong khi int& const getInt();bạn so sánh nó là dư thừa (tài liệu tham khảo đã là const) mặc dù hợp pháp và thường sẽ đưa ra cảnh báo. Dù sao, const trên một hàm thành viên thay đổi thiscon trỏ trong hàm từ Foo* constđến Foo const* const.
Nick Westgate

consttrên một chức năng thành viên không có nghĩa là tất cả cùng một điều hay là void set(int)&;một loại tham chiếu đến một chức năng?
Davis Herring
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.