Con trỏ so với tài liệu tham khảo


256

Điều gì sẽ được thực hành tốt hơn khi cung cấp cho một hàm biến ban đầu để làm việc với:

unsigned long x = 4;

void func1(unsigned long& val) {
     val = 5;            
}
func1(x);

hoặc là:

void func2(unsigned long* val) {
     *val = 5;
}
func2(&x);

IOW: Có bất kỳ lý do để chọn một hơn một?


1
Tài liệu tham khảo tất nhiên có giá trị, nhưng tôi đến từ C, nơi con trỏ ở khắp mọi nơi. Trước tiên, người ta phải thành thạo với con trỏ để hiểu giá trị của tài liệu tham khảo.
Jay D

Làm thế nào điều này phù hợp với một mục tiêu như tính minh bạch tham chiếu từ lập trình chức năng? Điều gì sẽ xảy ra nếu bạn luôn muốn các hàm trả về các đối tượng mới và không bao giờ thay đổi bên trong trạng thái, đặc biệt là không có các biến được truyền cho hàm. Có cách nào khái niệm này vẫn được sử dụng với các con trỏ và tham chiếu trong một ngôn ngữ như C ++. (Lưu ý, tôi giả sử một người nào đó đã có mục tiêu minh bạch referential Tôi không thích nói chuyện về việc có hay không nó là một mục tiêu tốt để có..)
ely

Thích tham khảo. Con trỏ người dùng khi bạn không có lựa chọn.
Ferruccio

Câu trả lời:


285

Nguyên tắc của tôi là:

Sử dụng các con trỏ nếu bạn muốn thực hiện số học con trỏ với chúng (ví dụ: tăng địa chỉ con trỏ để bước qua một mảng) hoặc nếu bạn phải truyền con trỏ NULL.

Sử dụng tài liệu tham khảo khác.


10
Điểm Excelent liên quan đến một con trỏ là NULL. Nếu bạn có một tham số con trỏ thì bạn phải kiểm tra rõ ràng rằng nó không phải là NULL hoặc tìm kiếm tất cả các công dụng của hàm để chắc chắn rằng nó không bao giờ là NULL. Nỗ lực này là không cần thiết cho tài liệu tham khảo.
Richard Corden

26
Giải thích ý của bạn bằng số học. Một người dùng mới có thể không hiểu rằng bạn muốn điều chỉnh những gì con trỏ đang trỏ vào.
Martin York

7
Martin, theo số học, ý tôi là bạn chuyển một con trỏ đến một cấu trúc nhưng biết rằng đó không phải là một cấu trúc đơn giản mà là một mảng của nó. Trong trường hợp này, bạn có thể lập chỉ mục cho nó bằng cách sử dụng [] hoặc thực hiện số học bằng cách sử dụng ++ / - trên con trỏ. Đó là sự khác biệt trong một tóm tắt.
Nils Pipenbrinck

2
Martin, Bạn chỉ có thể làm điều này với con trỏ trực tiếp. Không có tài liệu tham khảo. Chắc chắn bạn có thể lấy một con trỏ đến một tham chiếu và thực hiện điều tương tự trong thực tế, nhưng nếu bạn làm như vậy, bạn kết thúc bằng mã rất bẩn ..
Nils Pipenbrinck

1
Điều gì về đa hình (ví dụ Base* b = new Derived())? Đây có vẻ như là một trường hợp không thể được xử lý mà không có con trỏ.
Chris Redford

72

Tôi thực sự nghĩ rằng bạn sẽ được hưởng lợi từ việc thiết lập chức năng gọi các hướng dẫn mã hóa sau đây:

  1. Như ở tất cả những nơi khác, luôn luôn là chính xác const.

    • Lưu ý: Điều này có nghĩa là, trong số những thứ khác, chỉ các giá trị ngoài (xem mục 3) và các giá trị được truyền theo giá trị (xem mục 4) có thể thiếu constchỉ định.
  2. Chỉ truyền giá trị bằng con trỏ nếu giá trị 0 / NULL là đầu vào hợp lệ trong ngữ cảnh hiện tại.

    • Đặt vấn đề 1: Là một người gọi , bạn thấy rằng bất cứ điều gì bạn vượt qua đều phải ở trạng thái có thể sử dụng được.

    • Lý do 2: Khi gọi , bạn biết rằng bất cứ điều gì đi kèm trong trong tình trạng sử dụng được. Do đó, không cần kiểm tra NULL hoặc xử lý lỗi cho giá trị đó.

    • Đặt vấn đề 3: Rationales 1 và 2 sẽ được biên dịch thực thi . Luôn luôn bắt lỗi tại thời gian biên dịch nếu bạn có thể.

  3. Nếu một đối số hàm là một giá trị ngoài, thì chuyển nó bằng tham chiếu.

    • Đặt vấn đề: Chúng tôi không muốn phá vỡ mục 2 ...
  4. Chọn "truyền theo giá trị" qua "chuyển qua tham chiếu const" chỉ khi giá trị là POD ( Cơ sở hạ tầng cũ ) hoặc đủ nhỏ (thông minh bộ nhớ) hoặc theo cách khác đủ rẻ (theo thời gian) để sao chép.

    • Đặt vấn đề: Tránh các bản sao không cần thiết.
    • Lưu ý: đủ nhỏđủ rẻ không phải là số đo tuyệt đối.

Nó thiếu hướng dẫn khi: ... "khi nào nên sử dụng const &" ... Hướng dẫn 2 nên được viết "cho các giá trị [in], chỉ truyền bằng con trỏ nếu NULL hợp lệ. Nếu không, hãy sử dụng tham chiếu const (hoặc cho" các đối tượng nhỏ, sao chép) hoặc tham chiếu nếu đó là giá trị [ngoài]. Tôi đang theo dõi bài đăng này để có khả năng thêm +1.
paercebal

Mục 1 bao gồm trường hợp bạn mô tả.
Johann Gerell

Thật khó để vượt qua một tham số ngoài tham chiếu nếu nó không có khả năng mặc định. Điều đó khá phổ biến trong mã của tôi - toàn bộ lý do để có một hàm tạo đối tượng ngoài đó là vì nó không tầm thường.
MSalters

@MSalters: Nếu bạn sẽ phân bổ bộ nhớ bên trong hàm (mà tôi nghĩ là ý của bạn), thì tại sao không trả lại một con trỏ cho bộ nhớ được phân bổ?
Kleist

@Kleist: Thay mặt @MSalters, có nhiều lý do có thể. Một là bạn có thể đã được phân bổ bộ nhớ để lấp đầy, giống như kích thước sẵn std::vector<>.
Johann Gerell

24

Điều này cuối cùng kết thúc là chủ quan. Các cuộc thảo luận cho đến nay là hữu ích, nhưng tôi không nghĩ rằng có một câu trả lời chính xác hoặc quyết định cho vấn đề này. Rất nhiều sẽ phụ thuộc vào hướng dẫn phong cách và nhu cầu của bạn tại thời điểm đó.

Mặc dù có một số khả năng khác nhau (có hoặc không có gì đó có thể là NULL) với một con trỏ, sự khác biệt thực tế lớn nhất cho một tham số đầu ra hoàn toàn là cú pháp. Hướng dẫn về Phong cách C ++ của Google ( https://google.github.io/styleguide/cppguide.html#Reference_Argument ), ví dụ, chỉ bắt buộc các con trỏ cho các tham số đầu ra và chỉ cho phép các tham chiếu là const. Lý do là một trong những điều dễ đọc: một cái gì đó có cú pháp giá trị không nên có ý nghĩa ngữ nghĩa con trỏ. Tôi không cho rằng điều này nhất thiết đúng hay sai, nhưng tôi nghĩ vấn đề ở đây là vấn đề về phong cách chứ không phải sự chính xác.


Điều đó có nghĩa là các tham chiếu có cú pháp giá trị nhưng ý nghĩa ngữ nghĩa con trỏ?
Eric Andrew Lewis

Có vẻ như bạn đang chuyển một bản sao vì phần "truyền bằng tham chiếu" chỉ rõ ràng từ định nghĩa funciton (cú pháp giá trị), nhưng bạn không sao chép giá trị bạn vượt qua, về cơ bản bạn chuyển một con trỏ dưới mui xe, cho phép chức năng sửa đổi giá trị của bạn.
phant0m

Không nên quên rằng hướng dẫn về phong cách Google C ++ bị ghét nhiều.
Ded repeatator

7

Bạn nên vượt qua một con trỏ nếu bạn sẽ sửa đổi giá trị của biến. Mặc dù về mặt kỹ thuật chuyển một tham chiếu hoặc một con trỏ là như nhau, việc chuyển một con trỏ trong trường hợp sử dụng của bạn sẽ dễ đọc hơn vì nó "quảng cáo" thực tế là giá trị sẽ được thay đổi bởi hàm.


2
Nếu bạn làm theo hướng dẫn của Johann Gerell, một tham chiếu không phải là const cũng quảng cáo một biến có thể thay đổi, vì vậy con trỏ không có lợi thế đó ở đây.
Alexander Kondratskiy

4
@AlexanderKondratskiy: bạn đang thiếu điểm ... bạn không thể thấy ngay tại trang web cuộc gọi cho dù hàm được gọi có chấp nhận tham số là tham chiếu consthay không const, nhưng bạn có thể xem tham số đã vượt qua ala &xvs. xvà sử dụng sự đối lưu để mã hóa cho dù tham số có chịu trách nhiệm sửa đổi hay không. (Điều đó nói rằng, có những lúc bạn sẽ muốn vượt qua một constcon trỏ, do đó, sự thuyết phục chỉ là một gợi ý. Có thể nghi ngờ điều gì đó có thể được sửa đổi khi nó sẽ không nguy hiểm hơn là nó sẽ không xảy ra khi nó sẽ xảy ra ....)
Tony Delroy

5

Nếu bạn có một tham số trong đó bạn có thể cần chỉ ra sự vắng mặt của một giá trị, thì đó là cách thông thường để biến tham số thành giá trị con trỏ và truyền vào NULL.

Một giải pháp tốt hơn trong hầu hết các trường hợp (từ góc độ an toàn) là sử dụng boost :: tùy chọn . Điều này cho phép bạn chuyển các giá trị tùy chọn theo tham chiếu và cũng là giá trị trả về.

// Sample method using optional as input parameter
void PrintOptional(const boost::optional<std::string>& optional_str)
{
    if (optional_str)
    {
       cout << *optional_str << std::endl;
    }
    else
    {
       cout << "(no string)" << std::endl;
    }
}

// Sample method using optional as return value
boost::optional<int> ReturnOptional(bool return_nothing)
{
    if (return_nothing)
    {
       return boost::optional<int>();
    }

    return boost::optional<int>(42);
}


4

Con trỏ

  • Một con trỏ là một biến chứa một địa chỉ bộ nhớ.
  • Một khai báo con trỏ bao gồm một loại cơ sở, một * và tên biến.
  • Một con trỏ có thể trỏ đến bất kỳ số lượng biến nào trong đời
  • Một con trỏ hiện không trỏ đến một vị trí bộ nhớ hợp lệ sẽ được đưa ra giá trị null (Giá trị bằng 0)

    BaseType* ptrBaseType;
    BaseType objBaseType;
    ptrBaseType = &objBaseType;
  • & Là một toán tử đơn nguyên trả về địa chỉ bộ nhớ của toán hạng của nó.

  • Toán tử khử nhiễu (*) được sử dụng để truy cập giá trị được lưu trong biến mà con trỏ trỏ tới.

       int nVar = 7;
       int* ptrVar = &nVar;
       int nVar2 = *ptrVar;

Tài liệu tham khảo

  • Tham chiếu (&) giống như một bí danh cho một biến hiện có.

  • Một tham chiếu (&) giống như một con trỏ không đổi được tự động hủy đăng ký.

  • Nó thường được sử dụng cho danh sách đối số chức năng và giá trị trả về hàm.

  • Một tham chiếu phải được khởi tạo khi nó được tạo.

  • Khi một tham chiếu được khởi tạo cho một đối tượng, nó không thể được thay đổi để tham chiếu đến một đối tượng khác.

  • Bạn không thể có tài liệu tham khảo NULL.

  • Một tham chiếu const có thể đề cập đến một const int. Nó được thực hiện với một biến tạm thời có giá trị của const

    int i = 3;    //integer declaration
    int * pi = &i;    //pi points to the integer i
    int& ri = i;    //ri is refers to integer i – creation of reference and initialization

nhập mô tả hình ảnh ở đây

nhập mô tả hình ảnh ở đây


3

Một tài liệu tham khảo là một con trỏ ngầm. Về cơ bản, bạn có thể thay đổi giá trị của các điểm tham chiếu thành nhưng bạn không thể thay đổi tham chiếu để trỏ đến một thứ khác. Vì vậy, 2 xu của tôi là nếu bạn chỉ muốn thay đổi giá trị của một tham số thì hãy chuyển nó làm tham chiếu nhưng nếu bạn cần thay đổi tham số để trỏ đến một đối tượng khác thì hãy chuyển nó bằng con trỏ.


3

Hãy xem xét từ khóa của C #. Trình biên dịch yêu cầu người gọi của một phương thức để áp dụng từ khóa out cho bất kỳ đối số out nào, mặc dù nó đã biết nếu có. Điều này nhằm tăng cường khả năng đọc. Mặc dù với các IDE hiện đại, tôi có xu hướng nghĩ rằng đây là một công việc để làm nổi bật cú pháp (hoặc ngữ nghĩa).


lỗi đánh máy: ngữ nghĩa, không có nghĩa; +1 Tôi đồng ý về khả năng làm nổi bật thay vì viết ra (C #) hoặc & (trong trường hợp C, không có tài liệu tham khảo)
peenut

2

Chuyển qua tham chiếu const trừ khi có một lý do bạn muốn thay đổi / giữ nội dung bạn đang truyền vào.

Đây sẽ là phương pháp hiệu quả nhất trong hầu hết các trường hợp.

Hãy chắc chắn rằng bạn sử dụng const trên mỗi tham số mà bạn không muốn thay đổi, vì điều này không chỉ bảo vệ bạn khỏi làm điều gì đó ngu ngốc trong hàm, nó cung cấp một dấu hiệu tốt cho những người dùng khác biết hàm này làm gì với các giá trị được truyền. Điều này bao gồm tạo một con trỏ const khi bạn chỉ muốn thay đổi những gì được trỏ đến ...


2

Con trỏ:

  • Có thể được chỉ định nullptr(hoặc NULL).
  • Tại trang web cuộc gọi, bạn phải sử dụng &nếu loại của bạn không phải là một con trỏ, làm cho rõ ràng bạn đang sửa đổi đối tượng của mình.
  • Con trỏ có thể được phục hồi.

Người giới thiệu:

  • Không thể là null.
  • Một khi ràng buộc, không thể thay đổi.
  • Người gọi không cần sử dụng rõ ràng &. Điều này được coi là đôi khi xấu bởi vì bạn phải đi đến việc thực hiện chức năng để xem nếu tham số của bạn được sửa đổi.

Một điểm nhỏ cho những người không biết: nullptr hoặc NULL chỉ đơn giản là 0. stackoverflow.com/questions/462165/ chủ
Serguei Fedorov

2
nullptr không giống như 0. Hãy thử int a = nullptr; stackoverflow.com/questions/1282295/what-exactly-is-nullptr
Johan Lundberg

0

Tham chiếu tương tự như một con trỏ, ngoại trừ việc bạn không cần sử dụng tiền tố để truy cập giá trị được tham chiếu. Ngoài ra, một tham chiếu không thể được thực hiện để tham chiếu đến một đối tượng khác sau khi khởi tạo.

Tài liệu tham khảo đặc biệt hữu ích để chỉ định các đối số chức năng.

để biết thêm thông tin, hãy xem "Chuyến tham quan C ++" của "Bjarne Stroustrup" (2014) Trang 11-12

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.