Điểm của con trỏ const là gì?


149

Tôi không nói về con trỏ đến các giá trị const, mà là con trỏ chính nó.

Tôi đang học C và C ++ ngoài những thứ rất cơ bản và chỉ đến hôm nay tôi mới nhận ra rằng con trỏ được truyền theo giá trị cho các hàm, điều này có ý nghĩa. Điều này có nghĩa là bên trong một hàm tôi có thể làm cho con trỏ được sao chép trỏ đến một số giá trị khác mà không ảnh hưởng đến con trỏ ban đầu từ người gọi.

Vì vậy, điểm của việc có một tiêu đề chức năng nói:

void foo(int* const ptr);

Bên trong một chức năng như vậy, bạn không thể tạo điểm ptr cho một thứ khác bởi vì nó là const và bạn không muốn nó bị sửa đổi, nhưng một chức năng như thế này:

void foo(int* ptr);

Liệu công việc chỉ là tốt! bởi vì con trỏ được sao chép bằng mọi cách và con trỏ trong trình gọi không bị ảnh hưởng ngay cả khi bạn sửa đổi bản sao. Vậy lợi thế của const là gì?


29
Điều gì sẽ xảy ra nếu bạn muốn đảm bảo, tại thời điểm biên dịch, rằng con trỏ không thể và không nên được sửa đổi để trỏ đến một thứ khác?
Bạch kim Azure

25
Chính xác cùng một điểm với bất kỳ consttham số.
David Heffernan

2
@PlatinumAzure, tôi có một vài năm kinh nghiệm lập trình, nhưng thực hành phần mềm của tôi còn thiếu. Tôi rất vui vì tôi đã đưa ra câu hỏi này bởi vì tôi chưa bao giờ cảm thấy cần phải làm cho trình biên dịch chỉ ra các lỗi logic của riêng tôi. Phản ứng ban đầu của tôi tại thời điểm tôi gửi câu hỏi là "tại sao tôi phải quan tâm nếu con trỏ bị sửa đổi?, Nó sẽ không ảnh hưởng đến người gọi". Cho đến khi tôi đọc tất cả các câu trả lời tôi hiểu rằng tôi nên quan tâm bởi vì nếu tôi cố gắng sửa đổi nó, tôi và logic mã hóa của tôi đã sai (hoặc có một lỗi đánh máy như thiếu dấu hoa thị) và tôi có thể đã không nhận ra nếu trình biên dịch không ' nói với tôi
R. Ruiz.

3
@ R.Ruiz. Không nghi ngờ gì, ngay cả những người có kinh nghiệm nhất trong chúng ta cũng có thể làm với sự đảm bảo thêm về tính đúng đắn. Bởi vì công nghệ phần mềm là một dạng kỹ thuật mà có thể chấp nhận một chút thời gian chậm hơn (HTTP 502 ngẫu nhiên, kết nối chậm, hình ảnh không thể tải không phải là trường hợp đặc biệt, nhưng đôi khi một động cơ bị hỏng trong máy bay là không thể chấp nhận được và có khả năng nghiêm trọng), đôi khi chương trình con người với sự vội vàng không đáng có. Lập luận tương tự biện minh cho các bài kiểm tra đơn vị bằng văn bản sẽ giải thích việc sử dụng các constđảm bảo chính xác. Nó chỉ làm cho chúng tôi cảm thấy chắc chắn hơn rằng mã của chúng tôi là chính xác không thể chấp nhận được.
Bạch kim Azure

3
Một câu hỏi liên quan: stackoverflow.com/questions/219914/
Mạnh

Câu trả lời:


207

const là một công cụ mà bạn nên sử dụng để theo đuổi khái niệm C ++ rất quan trọng:

Tìm lỗi tại thời gian biên dịch, thay vì thời gian chạy, bằng cách yêu cầu trình biên dịch thực thi những gì bạn muốn nói.

Mặc dù nó không thay đổi chức năng, việc thêm constsẽ tạo ra lỗi trình biên dịch khi bạn đang làm những việc bạn không muốn làm. Hãy tưởng tượng lỗi đánh máy sau đây:

void foo(int* ptr)
{
    ptr = 0;// oops, I meant *ptr = 0
}

Nếu bạn sử dụng int* const, điều này sẽ tạo ra lỗi trình biên dịch vì bạn đang thay đổi giá trị thành ptr. Thêm các hạn chế thông qua cú pháp là một điều tốt nói chung. Đừng mang nó đi quá xa - ví dụ bạn đưa ra là một trường hợp mà hầu hết mọi người không bận tâm sử dụng const.


8
Cảm ơn bạn, đây là một câu trả lời thuyết phục tôi. Bạn đặt const để trình biên dịch cảnh báo bạn về các lỗi gán của riêng bạn. Ví dụ của bạn là hoàn hảo để minh họa khái niệm này bởi vì đó là một lỗi phổ biến với con trỏ. Hơn bạn!
R. Ruiz.

25
"Giúp trình biên dịch giúp bạn" là câu thần chú tôi thường tụng cho việc này.
Flexo

1
+1, nhưng đây là một trường hợp thú vị có thể
tranh cãi

3
Vì vậy, đó là câu trả lời tương tự mà bạn đưa ra cho "Tại sao đặt các biến riêng tư, khi bạn không thể sử dụng chúng bên ngoài lớp."
Lee Louviere

đây có thể là một chủ đề nhỏ, nhưng con trỏ const này nằm ở đâu trong bộ nhớ? Tôi biết tất cả các const ngồi trong phần toàn cầu của bộ nhớ, nhưng tôi không chắc chắn 100% về một const mà bạn được truyền vào dưới dạng var func. cảm ơn và xin lỗi nếu điều này không đúng chủ đề
Tomer

77

Tôi đưa ra quan điểm chỉ sử dụng các constđối số vì điều này cho phép kiểm tra trình biên dịch nhiều hơn: nếu tôi vô tình gán lại một giá trị đối số bên trong hàm, trình biên dịch sẽ cắn tôi.

Tôi hiếm khi sử dụng lại các biến, nó sẽ sạch hơn để tạo các biến mới để giữ các giá trị mới, vì vậy về cơ bản tất cả các khai báo biến của tôi là const(ngoại trừ một số trường hợp như biến vòng lặp constsẽ ngăn mã hoạt động).

Lưu ý rằng điều này chỉ có ý nghĩa trong định nghĩa của hàm. Nó không thuộc về khai báo , đó là những gì người dùng nhìn thấy. Và người dùng không quan tâm liệu tôi có sử dụng constcho các tham số bên trong hàm hay không.

Thí dụ:

// foo.h
int frob(int x);
// foo.cpp
int frob(int const x) {
   MyConfigType const config = get_the_config();
   return x * config.scaling;
}

Lưu ý cách cả đối số và biến cục bộ const. Không cần thiết nhưng với các chức năng thậm chí còn lớn hơn một chút, điều này đã nhiều lần cứu tôi khỏi mắc lỗi.


17
+1từ một constkẻ cuồng tín khác. Tuy nhiên, tôi thích trình biên dịch của tôi sủa tôi. Tôi mắc quá nhiều lỗi và sẽ đau khổ đến mức họ sẽ cắn.
sbi

2
+1, tôi đánh giá cao constchiến lược " trừ khi có lý do chính đáng". Có một số trường hợp ngoại lệ tốt, ví dụ: Sao chép và trao đổi
Flexo

2
Nếu tôi đang thiết kế một ngôn ngữ mới, các đối tượng được khai báo sẽ ở chế độ chỉ đọc ("const"). Bạn sẽ cần một số cú pháp đặc biệt, có lẽ là một từ khóa "var", để làm cho một đối tượng có thể ghi được.
Keith Thompson

@Keith, tôi rất muốn nói rằng, tất nhiên là trực tiếp. Mọi thứ khác đều ngu ngốc vào thời điểm này. Nhưng thật không may, hầu hết các nhà thiết kế ngôn ngữ dường như không đồng ý với chúng tôi.
Konrad Rudolph

1
@KubaOber Tôi không thấy nó bi quan như thế nào cả. Nếu sau này bạn thấy rằng bạn cần sửa đổi những gì đã được truyền trong phần thân của hàm, thì chỉ cần bỏ tham số constkhỏi đối số và tốt nhất là nhận xét tại sao. Một chút công việc nhỏ bé đó không biện minh cho việc đánh dấu tất cả các đối số là không consttheo mặc định và tự mở ra cho tất cả các lỗi tiềm ẩn tạo ra.
gạch dưới

20

Câu hỏi của bạn chạm vào một cái gì đó tổng quát hơn: Các đối số chức năng có nên là const không?

Hằng số của các đối số giá trị (như con trỏ của bạn) là một chi tiết triển khai và nó không phải là một phần của khai báo hàm. Điều này có nghĩa là chức năng của bạn luôn là thế này:

void foo(T);

Điều này hoàn toàn phụ thuộc vào người triển khai hàm cho dù cô ấy muốn sử dụng biến đối số phạm vi hàm theo cách biến đổi hoặc theo cách không đổi:

// implementation 1
void foo(T const x)
{
  // I won't touch x
  T y = x;
  // ...
}

// implementation 2
void foo(T x)
{
  // l33t coding skillz
  while (*x-- = zap()) { /* ... */ }
}

Vì vậy, hãy làm theo quy tắc đơn giản để không bao giờ đưa constvào khai báo (tiêu đề) và đặt nó trong định nghĩa (triển khai) nếu bạn không muốn hoặc cần sửa đổi biến.


5
Tôi đồng ý với điều này - nhưng tôi hơi khó chịu với ý tưởng làm cho tuyên bố và định nghĩa khác nhau. Đối với những thứ khác const, khai báo và (phần nguyên mẫu) định nghĩa nói chung sẽ giống hệt nhau.
Keith Thompson

@KeithThndry: Chà, bạn không cần phải làm điều đó nếu bạn không muốn. Chỉ cần không đưa constvào tuyên bố. Điều đó hoàn toàn phụ thuộc vào bạn nếu bạn muốn thêm vòng loại trong quá trình thực hiện.
Kerrek SB

1
Như tôi đã nói, tôi đồng ý rằng việc đưa constra tuyên bố nhưng không có định nghĩa. Nó chỉ cảm thấy như một trục trặc trong ngôn ngữ rằng đây là trường hợp duy nhất làm cho tuyên bố và định nghĩa không giống nhau có ý nghĩa. (Tất nhiên, hầu như không phải là trục trặc của C.)
Keith Thompson

16

Vòng loại const cấp cao nhất bị loại bỏ trong các khai báo, vì vậy các khai báo trong câu hỏi khai báo chính xác cùng chức năng. Mặt khác, trong định nghĩa (triển khai) trình biên dịch sẽ xác minh rằng nếu bạn đánh dấu con trỏ là const, nó không được sửa đổi bên trong thân hàm.


Tôi không biết điều đó. Vì vậy, nếu tôi cố gắng quá tải void foo (int * const ptr) với void foo (int * t ptr) tôi sẽ gặp lỗi trình biên dịch. Cảm ơn!
R. Ruiz.

@ R.Ruiz. Nếu sự giảm tốc của bạn là void foo (int * t ptr) và định nghĩa của bạn là void foo (int * const ptr), bạn sẽ KHÔNG gặp lỗi trình biên dịch.
Trevor Hickey

1
@TrevorHickey Họ sẽ ... nhưng không phải vì lý do họ nghĩ. int* t ptrlà một lỗi cú pháp. Không có điều đó, cả hai giống hệt nhau cho mục đích quá tải.
gạch dưới

14

Bạn nói đúng, đối với người gọi, nó hoàn toàn không có gì khác biệt. Nhưng đối với người viết hàm thì nó có thể là một mạng lưới an toàn "được thôi, tôi cần chắc chắn rằng tôi không đưa ra quan điểm sai lầm này". Không hữu ích nhưng cũng không vô dụng.

Về cơ bản, nó giống như có một int const the_answer = 42chương trình của bạn.


1
Điều gì quan trọng ở nơi họ chỉ ra nó, nó là một con trỏ được phân bổ trên ngăn xếp? Các chức năng không thể gây rối đó. Nó có ý nghĩa hơn nhiều để sử dụng các con trỏ chỉ đọc khi làm việc với các con trỏ trỏ tới. Nó không giống với int const! Tôi sẽ đánh giá thấp điều này chỉ vì tuyên bố sai lệch đó. const intint constlà tương đương, trong khi const int*int* constcó hai ý nghĩa khác nhau!
Lundin

1
@lundin Hãy giả sử chức năng này lớn và có những thứ khác trong đó. Vô tình người ta có thể làm cho nó trỏ đến một cái gì đó khác (trên ngăn xếp của hàm). Đây không phải là vấn đề đối với người gọi nhưng chắc chắn có thể là cho callee. Tôi không thấy bất cứ điều gì sai lệch trong các tuyên bố của mình: " đối với người viết hàm thì nó có thể là một mạng lưới an toàn".
cnicutar

1
@Lundin Về int constmột phần; Tôi đã đặt loại trước khi const (nghe có vẻ không tự nhiên) một thời gian và tôi nhận thức được sự khác biệt. Bài viết này có thể chứng minh hữu ích. Bản thân tôi đã có những lý do hơi khác nhau để chuyển sang phong cách này tuy nhiên.
cnicutar

1
Được chứ. Tôi chỉ viết một câu trả lời dài dòng của riêng tôi trong trường hợp người khác nhưng bạn và tôi đang đọc bài viết này. Tôi cũng bao gồm một lý do tại sao int const là phong cách xấu và const int là phong cách tốt.
Lundin

Lundin, tôi cũng đang đọc! Tôi đồng ý const int là phong cách tốt hơn mặc dù. @cnicutar, phản hồi đầu tiên của bạn đối với Lundin là điều tôi đang tìm kiếm khi tôi gửi câu hỏi này. Vì vậy, vấn đề không nằm ở người gọi (như tôi nghĩ không suy nghĩ), nhưng const là một sự đảm bảo cho sự bình tĩnh. Tôi thích ý tưởng này, cảm ơn bạn.
R. Ruiz.

14

Có rất nhiều consttừ khóa, nó là một từ khá phức tạp. Nói chung, việc thêm nhiều const vào chương trình của bạn được coi là thực hành lập trình tốt, tìm kiếm trên web cho "const đúng" và bạn sẽ tìm thấy nhiều thông tin về điều đó.

Từ khóa const là một "vòng loại", cái khác là volatilerestrict. Ít nhất là dễ bay hơi tuân theo các quy tắc (khó hiểu) tương tự như const.


Trước hết, từ khóa const phục vụ hai mục đích. Rõ ràng nhất là bảo vệ dữ liệu (và con trỏ) khỏi việc lạm dụng có chủ ý hoặc vô tình bằng cách làm cho chúng chỉ đọc. Mọi cố gắng sửa đổi một biến const sẽ được trình biên dịch phát hiện tại thời gian biên dịch.

Nhưng cũng có một mục đích khác trong bất kỳ hệ thống nào có bộ nhớ chỉ đọc, cụ thể là để đảm bảo rằng một biến nhất định được phân bổ bên trong bộ nhớ đó - ví dụ có thể là EEPROM hoặc flash. Chúng được gọi là ký ức không bay hơi, NVM. Một biến được phân bổ trong NVM tất nhiên vẫn sẽ tuân theo tất cả các quy tắc của biến const.

Có một số cách khác nhau để sử dụng consttừ khóa:

Khai báo một biến không đổi.

Điều này có thể được thực hiện như là

const int X=1; or
int const X=1;

Hai hình thức này là hoàn toàn tương đương . Phong cách thứ hai được coi là phong cách xấu và không nên được sử dụng.

Lý do tại sao hàng thứ hai bị coi là kiểu xấu, có lẽ là do "trình xác định lớp lưu trữ" như tĩnh và bên ngoài cũng có thể được khai báo sau loại thực tế, int staticv.v. bởi ủy ban C (dự thảo ISO 9899 N1539, 6.11.5). Do đó, để thống nhất, người ta cũng không nên viết vòng loại theo cách đó. Nó không phục vụ mục đích nào khác ngoài việc gây nhầm lẫn cho người đọc bằng mọi cách.

Khai báo một con trỏ tới một biến không đổi.

const int* ptr = &X;

Điều này có nghĩa là không thể sửa đổi nội dung của 'X'. Đây là cách thông thường mà bạn khai báo các con trỏ như thế này, chủ yếu là một phần của các tham số hàm cho "const orthness". Bởi vì 'X' thực sự không phải khai báo là const, nên nó có thể là bất kỳ biến nào. Nói cách khác, bạn luôn có thể "nâng cấp" một biến thành const. Về mặt kỹ thuật, C cũng cho phép hạ cấp từ const thành một biến đơn giản bằng các kiểu chữ rõ ràng, nhưng làm như vậy được coi là lập trình xấu và trình biên dịch thường đưa ra cảnh báo chống lại nó.

Khai báo một con trỏ không đổi

int* const ptr = &X;

Điều này có nghĩa là con trỏ chính nó là hằng số. Bạn có thể sửa đổi những gì nó trỏ đến, nhưng bạn không thể sửa đổi chính con trỏ. Điều này không có nhiều cách sử dụng, có một số ít, như đảm bảo rằng một con trỏ trỏ (con trỏ tới con trỏ) không thay đổi địa chỉ của nó trong khi chuyển dưới dạng tham số cho hàm. Bạn sẽ phải viết một cái gì đó không quá dễ đọc như thế này:

void func (int*const* ptrptr)

Tôi nghi ngờ nhiều lập trình viên C có thể có được const và * ngay trong đó. Tôi biết tôi không thể - tôi đã phải kiểm tra với GCC. Tôi nghĩ đó là lý do tại sao bạn hiếm khi thấy cú pháp đó cho con trỏ, mặc dù nó được coi là thực hành lập trình tốt.

Con trỏ không đổi cũng có thể được sử dụng để đảm bảo rằng chính biến con trỏ được khai báo trong bộ nhớ chỉ đọc, ví dụ bạn có thể muốn khai báo một số loại bảng tra cứu dựa trên con trỏ và phân bổ nó trong NVM.

Và tất nhiên, như được chỉ ra bởi các câu trả lời khác, con trỏ liên tục cũng có thể được sử dụng để thực thi "const đúng".

Khai báo một con trỏ không đổi thành dữ liệu không đổi

const int* const ptr=&X;

Đây là hai loại con trỏ được mô tả ở trên kết hợp với tất cả các thuộc tính của cả hai.

Khai báo hàm thành viên chỉ đọc (C ++)

Vì đây được gắn thẻ C ++, tôi cũng nên đề cập rằng bạn có thể khai báo các hàm thành viên của một lớp là const. Điều này có nghĩa là hàm không được phép sửa đổi bất kỳ thành viên nào khác của lớp khi nó được gọi, điều này vừa ngăn người lập trình của lớp khỏi các lỗi vô ý nhưng cũng thông báo cho người gọi về hàm thành viên rằng họ sẽ không làm phiền bất cứ điều gì lên bằng cách gọi nó Cú pháp là:

void MyClass::func (void) const;

8

... hôm nay tôi nhận ra rằng các con trỏ được truyền theo giá trị cho các hàm, điều này có ý nghĩa.

(imo) nó thực sự không có ý nghĩa như mặc định. mặc định hợp lý hơn là vượt qua dưới dạng con trỏ không thể gán lại ( int* const arg). đó là, tôi muốn các con trỏ được chuyển như các đối số được khai báo ngầm định.

Vậy lợi thế của const là gì?

lợi thế là nó đủ dễ và đôi khi không rõ ràng khi bạn sửa đổi địa chỉ mà đối số trỏ tới, để bạn có thể đưa ra một lỗi khi nó không dễ dàng. thay đổi địa chỉ là không điển hình. rõ ràng hơn để tạo một biến cục bộ nếu mục đích của bạn là sửa đổi địa chỉ. đồng thời, thao tác con trỏ thô là một cách dễ dàng để giới thiệu các lỗi.

vì vậy việc chuyển qua địa chỉ bất biến sẽ rõ ràng hơn và tạo một bản sao (trong những trường hợp không điển hình) khi bạn muốn thay đổi địa chỉ mà đối số trỏ tới:

void func(int* const arg) {
    int* a(arg);
    ...
    *a++ = value;
}

thêm rằng địa phương là hầu như miễn phí, và nó làm giảm cơ hội lỗi, trong khi cải thiện khả năng đọc.

ở mức cao hơn: nếu bạn đang thao tác đối số dưới dạng một mảng, nó thường rõ ràng hơn và ít xảy ra lỗi hơn cho máy khách để khai báo đối số là vùng chứa / bộ sưu tập.

nói chung, thêm const vào các giá trị, đối số và địa chỉ là một ý tưởng hay vì bạn không luôn nhận ra các tác dụng phụ mà trình biên dịch vui vẻ thực thi. do đó, nó hữu ích như const như được sử dụng trong một số trường hợp khác (ví dụ: câu hỏi tương tự như 'Tại sao tôi nên khai báo giá trị const?'). may mắn thay, chúng tôi cũng có tài liệu tham khảo, không thể được chỉ định lại.


4
+1 cho điều này là mặc định. C ++, giống như hầu hết các ngôn ngữ, có cách sai vòng. Thay vì có một consttừ khóa, nó nên có một mutabletừ khóa (tốt, nó có, nhưng với ngữ nghĩa sai).
Konrad Rudolph

2
Ý tưởng thú vị có const là mặc định. Cảm ơn câu trả lời của bạn!
R. Ruiz.

6

Nếu bạn thực hiện các hệ thống nhúng hoặc lập trình trình điều khiển thiết bị nơi bạn có các thiết bị ánh xạ bộ nhớ thì cả hai dạng 'const' thường được sử dụng, một dạng để ngăn con trỏ được gán lại (vì nó trỏ đến một địa chỉ phần cứng cố định.) Và, nếu ngoại vi đăng ký nó trỏ đến là một thanh ghi phần cứng chỉ đọc sau đó một const khác sẽ phát hiện rất nhiều lỗi tại thời gian biên dịch thay vì thời gian chạy.

Một thanh ghi chip ngoại vi 16 bit chỉ đọc có thể trông giống như:

static const unsigned short *const peripheral = (unsigned short *)0xfe0000UL;

Sau đó, bạn có thể dễ dàng đọc thanh ghi phần cứng mà không cần phải sử dụng ngôn ngữ lắp ráp:

input_word = *peripheral;


5

int iVal = 10; int * const ipPtr = & iVal;

Giống như một biến const bình thường, một con trỏ const phải được khởi tạo thành một giá trị khi khai báo và giá trị của nó không thể thay đổi.

Điều này có nghĩa là một con trỏ const sẽ luôn trỏ đến cùng một giá trị. Trong trường hợp trên, ipPtr sẽ luôn trỏ đến địa chỉ của iVal. Tuy nhiên, vì giá trị được trỏ đến vẫn không phải là hằng, nên có thể thay đổi giá trị được trỏ đến thông qua việc hủy bỏ con trỏ:

* ipPtr = 6; // được phép, vì pnPtr trỏ đến một int không const


5

Câu hỏi tương tự có thể được hỏi về bất kỳ loại nào khác (không chỉ con trỏ):

/* Why is n const? */
const char *expand(const int n) {
    if (n == 1) return "one";
    if (n == 2) return "two";
    if (n == 3) return "three";
    return "many";
}

LOL, bạn đã đúng. Trường hợp con trỏ xuất hiện trong đầu tôi vì tôi nghĩ chúng đặc biệt, nhưng chúng cũng giống như bất kỳ biến nào khác được truyền theo giá trị.
R. Ruiz.

5

Câu hỏi của bạn thực sự là nhiều hơn về lý do tại sao định nghĩa bất kỳ biến nào dưới dạng const không chỉ là tham số con trỏ tới hàm. Các quy tắc tương tự áp dụng ở đây như khi bạn xác định bất kỳ biến nào là hằng số, nếu đó là tham số cho hàm hoặc biến thành viên hoặc biến cục bộ.

Trong trường hợp cụ thể của bạn, về mặt chức năng, nó không tạo ra sự khác biệt như trong nhiều trường hợp khác khi bạn khai báo một biến cục bộ là const nhưng nó đặt ra một hạn chế là bạn không thể sửa đổi biến này.


Nhận xét của bạn làm cho nhận ra câu hỏi của tôi vô ích như thế nào bởi vì bạn đúng: đó là điều tương tự như có bất kỳ tham số nào khác như const. Nó có vẻ vô dụng nhưng nó ở đó để giúp lập trình viên có hạn chế này và tránh các lỗi
R. Ruiz.

4

Truyền một con trỏ const cho một hàm có ý nghĩa rất nhỏ, vì nó sẽ được truyền bằng giá trị. Đó chỉ là một trong những điều được thiết kế ngôn ngữ chung cho phép. Cấm nó chỉ vì nó không có ý nghĩa sẽ làm cho ngôn ngữ cụ thể. lớn hơn.

Nếu bạn đang ở trong một chức năng thì tất nhiên đó là một trường hợp khác. Có một con trỏ không thể thay đổi những gì nó trỏ đến là một khẳng định làm cho mã rõ ràng hơn.


4

Tôi đoán một lợi thế sẽ là trình biên dịch có thể thực hiện tối ưu hóa mạnh hơn bên trong hàm khi biết rằng con trỏ này không thể thay đổi.

Nó cũng tránh ví dụ. chuyển con trỏ này đến một chức năng con chấp nhận tham chiếu con trỏ không phải (và do đó có thể thay đổi con trỏ như thế void f(int *&p)), nhưng tôi đồng ý rằng tính hữu dụng có phần bị hạn chế trong trường hợp này.


+1 cho việc tối ưu hóa. Ít nhất những cuốn sách nói rằng nó đúng. Điều tôi muốn là hiểu chính xác những tối ưu hóa đó là gì
R. Ruiz.

4

Một ví dụ về nơi một con trỏ const có khả năng áp dụng cao có thể được chứng minh như vậy. Hãy xem xét bạn có một lớp với một mảng động bên trong nó và bạn muốn chuyển quyền truy cập của người dùng vào mảng nhưng không cấp cho họ quyền thay đổi con trỏ. Xem xét:

#include <new>
#include <string.h>

class TestA
{
    private:
        char *Array;
    public:
        TestA(){Array = NULL; Array = new (std::nothrow) char[20]; if(Array != NULL){ strcpy(Array,"Input data"); } }
        ~TestA(){if(Array != NULL){ delete [] Array;} }

        char * const GetArray(){ return Array; }
};

int main()
{
    TestA Temp;
    printf("%s\n",Temp.GetArray());
    Temp.GetArray()[0] = ' '; //You can still modify the chars in the array, user has access
    Temp.GetArray()[1] = ' '; 
    printf("%s\n",Temp.GetArray());
}

Sản xuất:

Dữ liệu đầu vào
đặt dữ liệu

Nhưng nếu chúng ta thử điều này:

int main()
{
    TestA Temp;
    printf("%s\n",Temp.GetArray());
    Temp.GetArray()[0] = ' ';
    Temp.GetArray()[1] = ' ';
    printf("%s\n",Temp.GetArray());
    Temp.GetArray() = NULL; //Bwuahahahaa attempt to set it to null
}

Chúng tôi nhận được:

lỗi: lvalue được yêu cầu như toán hạng bên trái của phép gán // Drat bị lỗi lần nữa!

Vì vậy, rõ ràng chúng ta có thể sửa đổi nội dung của mảng, nhưng không phải con trỏ của mảng. Tốt nếu bạn muốn đảm bảo con trỏ có trạng thái nhất quán khi chuyển nó lại cho người dùng. Có một nhược điểm, mặc dù:

int main()
{
    TestA Temp;
    printf("%s\n",Temp.GetArray());
    Temp.GetArray()[0] = ' ';
    Temp.GetArray()[1] = ' ';
    printf("%s\n",Temp.GetArray());
    delete [] Temp.GetArray(); //Bwuahaha this actually works!
}

Chúng ta vẫn có thể xóa tham chiếu bộ nhớ của con trỏ, ngay cả khi chúng ta không thể sửa đổi chính con trỏ.

Vì vậy, nếu bạn muốn tham chiếu bộ nhớ luôn luôn trỏ đến một cái gì đó (IE không bao giờ được sửa đổi, tương tự như cách một tham chiếu hiện đang hoạt động), thì nó rất có thể áp dụng. Nếu bạn muốn người dùng có quyền truy cập đầy đủ và sửa đổi nó, thì non-const là dành cho bạn.

Biên tập:

Sau khi nhận thấy okorz001 nhận xét về việc không thể gán do GetArray () là toán hạng giá trị đúng, nhận xét của anh ta hoàn toàn chính xác, nhưng ở trên vẫn áp dụng nếu bạn trả về một tham chiếu cho con trỏ (tôi cho rằng tôi cho rằng GetArray là tham chiếu một tài liệu tham khảo), ví dụ:

class TestA
{
    private:
        char *Array;
    public:
        TestA(){Array = NULL; Array = new (std::nothrow) char[20]; if(Array != NULL){ strcpy(Array,"Input data"); } }
        ~TestA(){if(Array != NULL){ delete [] Array;} }

        char * const &GetArray(){ return Array; } //Note & reference operator
        char * &GetNonConstArray(){ return Array; } //Note non-const
};

int main()
{
    TestA Temp;
    Temp.GetArray() = NULL; //Returns error
    Temp.GetNonConstArray() = NULL; //Returns no error
}

Sẽ trở lại trong lần đầu tiên dẫn đến một lỗi:

lỗi: gán vị trí chỉ đọc 'Temp.TestA :: GetArray ()'

Nhưng lần thứ hai sẽ xảy ra vui vẻ bất chấp hậu quả tiềm ẩn ở bên dưới.

Rõ ràng, câu hỏi sẽ được đặt ra 'tại sao bạn muốn trả lại một tham chiếu cho một con trỏ'? Có những trường hợp hiếm hoi mà bạn cần gán bộ nhớ (hoặc dữ liệu) trực tiếp cho con trỏ ban đầu được đề cập (ví dụ: xây dựng malloc / free hoặc new / free front-end của riêng bạn), nhưng trong những trường hợp đó, nó là một tham chiếu không phải là const . Tham chiếu đến một con trỏ const Tôi không gặp phải tình huống sẽ bảo đảm nó (trừ khi có thể là các biến tham chiếu const được khai báo thay vì các kiểu trả về?).

Hãy xem xét nếu chúng ta có một hàm lấy một con trỏ const (so với một hàm không):

class TestA
{
    private:
        char *Array;
    public:
        TestA(){Array = NULL; Array = new (std::nothrow) char[20]; if(Array != NULL){ strcpy(Array,"Input data"); } }
        ~TestA(){if(Array != NULL){ delete [] Array;} }

        char * const &GetArray(){ return Array; }

        void ModifyArrayConst(char * const Data)
        {
            Data[1]; //This is okay, this refers to Data[1]
            Data--; //Produces an error. Don't want to Decrement that.
            printf("Const: %c\n",Data[1]);
        }

        void ModifyArrayNonConst(char * Data)
        {
            Data--; //Argh noo what are you doing?!
            Data[1]; //This is actually the same as 'Data[0]' because it's relative to Data's position
            printf("NonConst: %c\n",Data[1]);
        }
};

int main()
{
    TestA Temp;
    Temp.ModifyArrayNonConst("ABCD");
    Temp.ModifyArrayConst("ABCD");
}

Các lỗi trong const tạo ra thông báo như vậy:

lỗi: giảm tham số chỉ đọc 'Dữ liệu'

Điều này là tốt vì có lẽ chúng ta không muốn làm điều đó, trừ khi chúng ta muốn gây ra các vấn đề được biểu thị trong các bình luận. Nếu chúng ta chỉnh sửa phần giảm trong hàm const, điều sau đây xảy ra:

NonConst: A
Const: B

Rõ ràng, mặc dù A là 'Dữ liệu [1]', nhưng nó được coi là 'Dữ liệu [0]' vì con trỏ NonConst cho phép hoạt động giảm dần. Với const được triển khai, như một người khác viết, chúng tôi bắt được lỗi tiềm ẩn trước khi nó xảy ra.

Một cân nhắc chính khác, đó là một con trỏ const có thể được sử dụng làm tham chiếu giả, trong đó điều mà các điểm tham chiếu không thể thay đổi (người ta tự hỏi, nếu có lẽ đây là cách nó được thực hiện). Xem xét:

int main()
{
    int A = 10;
    int * const B = &A;
    *B = 20; //This is permitted
    printf("%d\n",A);
    B = NULL; //This produces an error
}

Khi cố gắng biên dịch, tạo ra lỗi sau:

lỗi: gán biến chỉ đọc 'B'

Đó có lẽ là một điều xấu nếu muốn tham chiếu liên tục đến A. Nếu B = NULLđược nhận xét, trình biên dịch sẽ vui lòng cho chúng tôi sửa đổi *Bvà do đó A. Điều này có vẻ không hữu ích với ints, nhưng hãy xem xét nếu bạn có một lập trường duy nhất của một ứng dụng đồ họa trong đó bạn muốn một con trỏ không thể thay đổi được đề cập đến mà bạn có thể vượt qua xung quanh.

Cách sử dụng của nó là khác nhau (xin lỗi vì chơi chữ ngoài ý muốn), nhưng được sử dụng một cách chính xác, nó là một công cụ khác trong hộp để hỗ trợ lập trình.


2
Temp.GetArray() = NULLthất bại vì Temp.GetArray()là một giá trị, không phải vì nó đủ điều kiện. Ngoài ra, tôi tin rằng const-Qualifier bị tước khỏi tất cả các loại trả về.
Oscar Korz

@ okorz001: Sau khi thử nghiệm, bạn thực sự đúng. Tuy nhiên, điều trên áp dụng nếu bạn trả về một tham chiếu đến chính con trỏ. Tôi sẽ chỉnh sửa bài viết của tôi cho phù hợp.
SSight3

3

Không có gì đặc biệt về con trỏ mà bạn sẽ không bao giờ muốn chúng là const. Giống như bạn có thể có intcác giá trị hằng số thành viên lớp , bạn cũng có thể có các con trỏ liên tục vì những lý do tương tự: Bạn muốn đảm bảo rằng không ai thay đổi những gì được chỉ ra. Các tham chiếu C ++ phần nào giải quyết điều này, nhưng hành vi con trỏ được kế thừa từ C.



3

Các kiểu khai báo bất kỳ biến nào như-
(1) Khai báo một biến không đổi.
DataType const varibleName;

 int const x;
    x=4; //you can assign its value only One time
(2) Khai báo một con trỏ tới một biến không đổi
const dataType* PointerVaribleName=&X;
 const int* ptr = &X;
     //Here pointer variable refer contents of 'X' that is const Such that its cannot be modified
dataType* const PointerVaribleName=&X;
 int* const ptr = &X;
     //Here pointer variable itself is constant  Such that value of 'X'  can be modified But pointer can't be modified


Chúng tôi cung cấp một biến con trỏ dưới dạng const thành một hàm làm đối số khi chúng tôi không muốn thay đổi giá trị thực của nó
Pyadav
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.