Tại sao các đối tượng được thông qua tham chiếu?


31

Một đồng nghiệp trẻ đang nghiên cứu OO đã hỏi tôi tại sao mọi đối tượng được thông qua tham chiếu, điều này trái ngược với các kiểu nguyên thủy hoặc cấu trúc. Đó là một đặc điểm chung của các ngôn ngữ như Java và C #.

Tôi không thể tìm thấy một câu trả lời tốt cho anh ta.

Các động lực cho quyết định thiết kế này là gì? Các nhà phát triển của các ngôn ngữ này có mệt mỏi khi phải tạo con trỏ và typedefs mỗi lần không?


2
Bạn có hỏi tại sao Java và C # có bạn truyền tham số theo tham chiếu thay vì theo giá trị hoặc bằng tham chiếu thay vì bằng con trỏ không?
robert

@Robert, ở cấp độ cao có sự khác biệt nào giữa "tham chiếu thay vì bằng con trỏ" không? Bạn có nghĩ rằng tôi nên thay đổi tiêu đề thành một cái gì đó như 'tại sao đối tượng luôn được tham chiếu không? "?
Gustavo Cardoso

Tài liệu tham khảo là con trỏ.
compman

2
@Anto: Một tham chiếu Java về mọi mặt giống hệt với một con trỏ C được sử dụng đúng cách (được sử dụng đúng cách: không phải kiểu truyền, không được đặt thành bộ nhớ không hợp lệ, không được đặt theo nghĩa đen).
Zan Lynx

5
Ngoài ra để thực sự mang tính mô phạm, tiêu đề không chính xác (ít nhất là liên quan đến .net). Các đối tượng KHÔNG được truyền bằng tham chiếu, tham chiếu được truyền theo giá trị. Khi bạn truyền một đối tượng cho một phương thức, giá trị tham chiếu được sao chép sang một tham chiếu mới trong thân phương thức. Tôi nghĩ thật xấu hổ khi "các đối tượng được thông qua tham chiếu" đã lọt vào danh sách các trích dẫn lập trình phổ biến khi nó không chính xác và dẫn đến sự hiểu biết kém hơn về các tài liệu tham khảo cho các lập trình viên mới bắt đầu.
SecretDeveloper

Câu trả lời:


15

Những lý do cơ bản dẫn đến điều này:

  • Con trỏ là kỹ thuật để có được quyền
  • Bạn cần con trỏ để thực hiện các cấu trúc dữ liệu nhất định
  • Bạn cần con trỏ để có hiệu quả trong việc sử dụng bộ nhớ
  • Bạn không cần lập chỉ mục bộ nhớ thủ công để hoạt động nếu bạn không sử dụng phần cứng trực tiếp.

Do đó, tài liệu tham khảo.


32

Trả lời đơn giản:

Giảm thiểu mức tiêu thụ bộ nhớ

thời gian CPU trong việc tái tạo và thực hiện một bản sao sâu của mọi đối tượng được truyền ở đâu đó.


Tôi đồng ý với bạn, nhưng tôi nghĩ rằng cũng có một số động lực thiết kế thẩm mỹ hoặc OO dọc theo những cái này.
Gustavo Cardoso

9
@Gustavo Cardoso: "một số động lực thiết kế thẩm mỹ hoặc OO". Không. Nó chỉ đơn giản là một sự tối ưu hóa.
S.Lott

6
@ S.Lott: Không, nó có ý nghĩa trong các thuật ngữ OO để vượt qua tham chiếu bởi vì, về mặt ngữ nghĩa, bạn không muốn tạo các bản sao của các đối tượng. Bạn muốn vượt qua đối tượng chứ không phải là một bản sao của nó. Nếu bạn đang vượt qua giá trị, nó sẽ phá vỡ ẩn dụ OO phần nào vì bạn đã có tất cả các bản sao này của các đối tượng được tạo ở khắp nơi không có ý nghĩa ở cấp độ cao hơn.
trực giác

@Gustavo: Tôi nghĩ chúng ta đang tranh luận về cùng một điểm. Bạn đề cập đến ngữ nghĩa của OOP và đề cập đến phép ẩn dụ của OOP là những lý do bổ sung cho riêng tôi. Dường như với tôi, những người tạo ra OOP đã làm theo cách họ đã làm để "giảm thiểu mức tiêu thụ bộ nhớ" và "Tiết kiệm thời gian của CPU"
Tim

14

Trong C ++, bạn có hai tùy chọn chính: trả về theo giá trị hoặc trả về theo con trỏ. Hãy nhìn vào cái đầu tiên:

MyClass getNewObject() {
    MyClass newObj;
    return newObj;
}

Giả sử trình biên dịch của bạn không đủ thông minh để sử dụng tối ưu hóa giá trị trả về, điều xảy ra ở đây là:

  • newObj được xây dựng như một đối tượng tạm thời và được đặt trên ngăn xếp cục bộ.
  • Một bản sao của newObj được tạo và trả lại.

Chúng tôi đã tạo một bản sao của đối tượng một cách vô nghĩa. Đây là một sự lãng phí thời gian xử lý.

Thay vào đó, hãy nhìn vào con trỏ trở lại:

MyClass* getNewObject() {
    MyClass newObj = new MyClass();
    return newObj;
}

Chúng tôi đã loại bỏ bản sao dư thừa, nhưng bây giờ chúng tôi đã đưa ra một vấn đề khác: chúng tôi đã tạo một đối tượng trên đống mà sẽ không tự động bị phá hủy. Chúng ta phải tự giải quyết nó:

MyClass someObj = getNewObject();
delete someObj;

Biết ai chịu trách nhiệm xóa một đối tượng được phân bổ theo cách này là điều chỉ có thể được truyền đạt bằng các bình luận hoặc theo quy ước. Nó dễ dàng dẫn đến rò rỉ bộ nhớ.

Rất nhiều cách giải quyết đã được đề xuất để giải quyết hai vấn đề này - tối ưu hóa giá trị trả về (trong đó trình biên dịch đủ thông minh để không tạo bản sao dự phòng theo giá trị trả về), chuyển tham chiếu đến phương thức (để hàm đưa vào đối tượng hiện có thay vì tạo một đối tượng mới), con trỏ thông minh (để câu hỏi về quyền sở hữu là tranh luận).

Những người tạo Java / C # nhận ra rằng luôn trả về đối tượng bằng tham chiếu là một giải pháp tốt hơn, đặc biệt nếu ngôn ngữ hỗ trợ nó nguyên bản. Nó liên quan đến rất nhiều tính năng khác mà các ngôn ngữ có, chẳng hạn như bộ sưu tập rác, v.v.


Trả về giá trị là đủ tệ, nhưng giá trị vượt qua thậm chí còn tồi tệ hơn khi nói đến các đối tượng và tôi nghĩ đó là vấn đề thực sự mà họ đang cố gắng tránh.
Mason Wheeler

chắc chắn bạn có một điểm hợp lệ Nhưng vấn đề thiết kế OO mà @Mason chỉ ra là động lực cuối cùng của sự thay đổi. Không có ý nghĩa gì để giữ sự khác biệt giữa tham chiếu và giá trị khi bạn chỉ muốn sử dụng tham chiếu.
Gustavo Cardoso

10

Nhiều câu trả lời khác có thông tin tốt. Tôi muốn thêm một điểm quan trọng về nhân bản chỉ được giải quyết một phần.

Sử dụng tài liệu tham khảo là thông minh. Sao chép mọi thứ là nguy hiểm.

Như những người khác đã nói, trong Java, không có "bản sao" tự nhiên. Đây không chỉ là một tính năng bị thiếu. Bạn không bao giờ muốn chỉ sao chép willy-nilly * (dù nông hay sâu) mỗi thuộc tính trong một đối tượng. Điều gì nếu tài sản đó là một kết nối cơ sở dữ liệu? Bạn không thể "nhân bản" một kết nối cơ sở dữ liệu nữa mà bạn có thể sao chép một con người. Khởi tạo tồn tại vì một lý do.

Bản sao sâu là một vấn đề của riêng họ - bạn thực sự đi sâu đến mức nào? Bạn chắc chắn không thể sao chép bất cứ thứ gì tĩnh (bao gồm mọi Classđối tượng).

Vì vậy, với cùng một lý do tại sao không có bản sao tự nhiên, các đối tượng được truyền dưới dạng bản sao sẽ tạo ra sự điên rồ . Ngay cả khi bạn có thể "sao chép" kết nối DB - làm thế nào bây giờ bạn có thể đảm bảo rằng nó đã bị đóng?


* Xem các bình luận - Theo tuyên bố "không bao giờ" này, tôi có nghĩa là một bản sao tự động nhân bản mọi tài sản. Java đã không cung cấp một cái và có lẽ bạn không phải là một người sử dụng ngôn ngữ để tạo ngôn ngữ của riêng bạn, vì những lý do được liệt kê ở đây. Chỉ nhân bản các trường không tạm thời sẽ là một sự khởi đầu, nhưng ngay cả khi đó bạn cần phải siêng năng xác định transientnơi thích hợp.


Tôi gặp khó khăn trong việc hiểu bước nhảy từ phản đối tốt sang nhân bản trong một số điều kiện nhất định để tuyên bố rằng nó không bao giờ cần thiết. Và tôi đã gặp phải tình huống mà một bản sao chính xác là cần thiết, nơi không có chức năng tĩnh nơi có liên quan, không có IO hoặc kết nối mở có thể là ở vấn đề ... Tôi hiểu những rủi ro của nhân bản, nhưng tôi không thể nhìn thấy chăn bao giờ .
Inca

2
@Inca - Bạn có thể đang hiểu lầm tôi. Cố ý thực hiện clonelà tốt. "Willy-nilly" Tôi có nghĩa là sao chép tất cả các thuộc tính mà không nghĩ về nó - không có mục đích. Các nhà thiết kế ngôn ngữ Java đã buộc ý định này bằng cách yêu cầu triển khai do người dùng tạo clone.
Nicole

Sử dụng tài liệu tham khảo cho các đối tượng bất biến là thông minh. Tạo các giá trị đơn giản như Ngày có thể thay đổi, và sau đó tạo nhiều tham chiếu đến chúng thì không.
kevin cline

@NickC: Lý do chính "nhân bản mọi thứ willy nilly" là nguy hiểm là các ngôn ngữ / khung như Java và .net không có bất kỳ phương tiện nào để chỉ định khai báo liệu tham chiếu có đóng gói trạng thái có thể thay đổi, cả hai hay không. Nếu trường chứa tham chiếu đối tượng đóng gói trạng thái có thể thay đổi nhưng không nhận dạng, thì nhân bản đối tượng yêu cầu đối tượng giữ trạng thái được sao chép và tham chiếu đến bản sao đó được lưu trữ trong trường. Nếu tham chiếu đóng gói danh tính nhưng không thể thay đổi trạng thái, trường trong bản sao phải tham chiếu đến cùng một đối tượng như trong bản gốc.
supercat

Các khía cạnh sao chép sâu là một điểm quan trọng. Sao chép các đối tượng là vấn đề khi chúng chứa các tham chiếu đến các đối tượng khác, đặc biệt nếu biểu đồ đối tượng chứa các đối tượng có thể thay đổi.
Caleb

4

Các đối tượng luôn được tham chiếu trong Java. Họ không bao giờ được thông qua xung quanh mình.

Một lợi thế là điều này đơn giản hóa ngôn ngữ. Một đối tượng C ++ có thể được biểu diễn dưới dạng giá trị hoặc tham chiếu, tạo ra nhu cầu sử dụng hai toán tử khác nhau để truy cập thành viên: .->. (Có nhiều lý do tại sao điều này không thể được hợp nhất; ví dụ, con trỏ thông minh là các giá trị là tham chiếu và phải giữ những khác biệt đó.) Java chỉ cần. .

Một lý do khác là tính đa hình phải được thực hiện bằng cách tham chiếu, không phải giá trị; một đối tượng được xử lý bởi giá trị chỉ ở đó và có một loại cố định. Có thể làm hỏng điều này trong C ++.

Ngoài ra, Java có thể chuyển đổi gán / sao chép mặc định / bất cứ thứ gì. Trong C ++, đó là một bản sao sâu hơn hoặc ít hơn, trong khi trong Java, nó là một phép gán / sao chép con trỏ đơn giản / bất cứ thứ gì, .clone()và trong trường hợp bạn cần sao chép.


Đôi khi nó trở nên thực sự xấu xí khi bạn sử dụng '(* đối tượng) ->'
Gustavo Cardoso

1
Điều đáng chú ý là C ++ phân biệt giữa con trỏ, tham chiếu và giá trị. Một số kính * là một con trỏ đến một đối tượng. SomeClass & là một tham chiếu đến một đối tượng. Một số loại kính là một loại giá trị.
Ant

Tôi đã hỏi @Rober về câu hỏi ban đầu, nhưng tôi cũng sẽ làm điều đó ở đây: sự khác biệt giữa * và & trên C ++ chỉ là một thứ kỹ thuật cấp thấp, phải không? Có phải họ, ở cấp độ cao, về mặt ngữ nghĩa họ giống nhau.
Gustavo Cardoso

3
@Gustavo Cardoso: Sự khác biệt là ngữ nghĩa; ở mức độ kỹ thuật thấp, chúng thường giống hệt nhau. Một con trỏ trỏ đến một đối tượng hoặc là NULL (một giá trị xấu được xác định). Trừ khi const, giá trị của nó có thể được thay đổi để trỏ đến các đối tượng khác. Tham chiếu là tên khác của một đối tượng, không thể là NULL và không thể được nối lại. Nó thường được thực hiện bằng cách sử dụng con trỏ đơn giản, nhưng đó là một chi tiết triển khai.
David Thornley

+1 cho "đa hình phải được thực hiện bằng cách tham chiếu." Đó là một chi tiết cực kỳ quan trọng mà hầu hết các câu trả lời khác đã bỏ qua.
Doval

4

Tuyên bố ban đầu của bạn về các đối tượng C # được truyền bằng tham chiếu là không chính xác. Trong C #, các đối tượng là các loại tham chiếu, nhưng theo mặc định, chúng được truyền theo giá trị giống như các loại giá trị. Trong trường hợp của kiểu tham chiếu, "giá trị" đang được sao chép dưới dạng tham số phương thức pass-by-value là chính tham chiếu, do đó, các thay đổi đối với các thuộc tính bên trong một phương thức sẽ được phản ánh bên ngoài phạm vi phương thức.

Tuy nhiên, nếu bạn đã tự gán lại biến tham số trong một phương thức, bạn sẽ thấy thay đổi này không được phản ánh bên ngoài phạm vi phương thức. Ngược lại, nếu bạn thực sự vượt qua một tham số bằng cách tham chiếu bằng reftừ khóa, hành vi này hoạt động như mong đợi.


3

Câu trả lời nhanh

Các nhà thiết kế của Java và các ngôn ngữ tương tự muốn áp dụng khái niệm "mọi thứ là một đối tượng". Và truyền dữ liệu dưới dạng tham chiếu rất nhanh và không tiêu tốn nhiều bộ nhớ.

Thêm bình luận nhàm chán mở rộng

Altougth, những ngôn ngữ đó sử dụng các tham chiếu đối tượng (Java, Delphi, C #, VB.NET, Vala, Scala, PHP), sự thật là các tham chiếu đối tượng là con trỏ tới các đối tượng được ngụy trang. Giá trị null, cấp phát bộ nhớ, sao chép tham chiếu mà không sao chép toàn bộ dữ liệu của một đối tượng, tất cả chúng đều là con trỏ đối tượng, không phải đối tượng đơn giản !!!

Trong Object Pascal (không phải Delphi), anc C ++ (không phải Java, không phải C #), một đối tượng có thể được khai báo là biến phân bổ tĩnh và cũng với biến được cấp phát động, thông qua việc sử dụng một con trỏ ("tham chiếu đối tượng" mà không có " cú pháp đường "). Mỗi trường hợp sử dụng cú pháp nhất định và không có cách nào để bị nhầm lẫn như trong Java "và bạn bè". Trong các ngôn ngữ đó, một đối tượng có thể được truyền cả dưới dạng giá trị hoặc tham chiếu.

Lập trình viên biết khi nào cần một cú pháp con trỏ và khi nào không bắt buộc, nhưng trong ngôn ngữ Java và các ngôn ngữ giống nhau, điều này thật khó hiểu.

Trước khi Java tồn tại hoặc trở thành xu hướng, nhiều lập trình viên đã học OO trong C ++ mà không cần con trỏ, chuyển qua giá trị hoặc bằng tham chiếu khi được yêu cầu. Khi chuyển từ học tập sang ứng dụng kinh doanh., Sau đó, họ thường sử dụng các con trỏ đối tượng. Thư viện QT là ví dụ tốt về điều đó.

Khi tôi học Java, tôi đã cố gắng làm theo mọi thứ là một khái niệm đối tượng, nhưng bị lẫn lộn trong mã hóa. Cuối cùng, tôi đã nói "ok, đây là các đối tượng được phân bổ động bằng một con trỏ với cú pháp của một đối tượng được phân bổ tĩnh" và một lần nữa không gặp khó khăn khi viết mã.


3

Java và C # kiểm soát bộ nhớ cấp thấp từ bạn. "Heap" nơi các đối tượng bạn tạo ra sống cuộc sống của chính nó; ví dụ, trình thu gom rác gặt các đối tượng bất cứ khi nào nó thích.

Vì có một lớp gián tiếp riêng biệt giữa chương trình của bạn và "heap" đó, hai cách để chỉ một đối tượng, theo giá trị và theo con trỏ (như trong C ++), không thể phân biệt được : bạn luôn đề cập đến các đối tượng "bằng con trỏ" đâu đó trong đống. Đó là lý do tại sao phương pháp thiết kế như vậy làm cho tham chiếu qua ngữ nghĩa mặc định của phép gán. Java, C #, Ruby, et cetera.

Ở trên chỉ quan tâm đến các ngôn ngữ bắt buộc. Trong các ngôn ngữ kể trên, kiểm soát bộ nhớ được thông qua vào thời gian chạy, nhưng thiết kế ngôn ngữ cũng nói "hey, nhưng trên thực tế, có bộ nhớ, và có được các đối tượng, và họ làm chiếm bộ nhớ". Các ngôn ngữ chức năng trừu tượng hơn nữa, bằng cách loại trừ khái niệm "bộ nhớ" khỏi định nghĩa của chúng. Đó là lý do tại sao tham chiếu qua không nhất thiết phải áp dụng cho tất cả các ngôn ngữ mà bạn không kiểm soát bộ nhớ cấp thấp.


2

Tôi có thể nghĩ ra một vài lý do:

  • Sao chép các kiểu nguyên thủy là không đáng kể, nó thường dịch sang một lệnh máy.

  • Sao chép các đối tượng không phải là nhỏ, đối tượng có thể chứa các thành viên là chính các đối tượng. Sao chép các đối tượng là tốn kém về thời gian và bộ nhớ CPU. Thậm chí có nhiều cách sao chép một đối tượng tùy thuộc vào ngữ cảnh.

  • Truyền đối tượng bằng tham chiếu là rẻ và nó cũng trở nên tiện dụng khi bạn muốn chia sẻ / cập nhật thông tin đối tượng giữa nhiều máy khách của đối tượng.

  • Các cấu trúc dữ liệu phức tạp (đặc biệt là các cấu trúc đệ quy) yêu cầu con trỏ. Vượt qua các đối tượng bằng cách tham chiếu chỉ là một cách an toàn hơn để chuyển con trỏ.


1

Bởi vì nếu không, hàm sẽ có thể tự động tạo một bản sao (rõ ràng sâu) của bất kỳ loại đối tượng nào được truyền cho nó. Và thường thì nó không thể đoán ra để làm cho nó. Vì vậy, bạn sẽ phải xác định việc thực hiện phương thức sao chép / sao chép cho tất cả các đối tượng / lớp của bạn.


Nó có thể chỉ làm một bản sao nông và giữ các giá trị thực tế và con trỏ tới các đối tượng khác không?
Gustavo Cardoso

#Gustavo Cardoso, sau đó bạn có thể sửa đổi các đối tượng khác thông qua đối tượng này, đó là những gì bạn mong đợi từ một đối tượng KHÔNG được thông qua dưới dạng tham chiếu?
David

0

Bởi vì Java được thiết kế như một C ++ tốt hơn và C # được thiết kế như một Java tốt hơn và các nhà phát triển của các ngôn ngữ này đã mệt mỏi với mô hình đối tượng C ++ bị phá vỡ cơ bản, trong đó các đối tượng là các loại giá trị.

Hai trong ba nguyên tắc cơ bản của lập trình hướng đối tượng là kế thừa và đa hình, và coi các đối tượng là các loại giá trị thay vì các kiểu tham chiếu tàn phá cả hai. Khi bạn truyền một đối tượng cho một hàm làm tham số, trình biên dịch cần biết có bao nhiêu byte để vượt qua. Khi đối tượng của bạn là loại tham chiếu, câu trả lời rất đơn giản: kích thước của một con trỏ, giống nhau cho tất cả các đối tượng. Nhưng khi đối tượng của bạn là một loại giá trị, nó phải vượt qua kích thước thực của giá trị. Vì một lớp dẫn xuất có thể thêm các trường mới, điều này có nghĩa là sizeof (dẫn xuất)! = Sizeof (cơ sở) và đa hình đi ra ngoài cửa sổ.

Đây là một chương trình C ++ tầm thường thể hiện vấn đề:

#include <iostream> 
class Parent 
{ 
public: 
   int a;
   int b;
   int c;
   Parent(int ia, int ib, int ic) { 
      a = ia; b = ib; c = ic;
   };
   virtual void doSomething(void) { 
      std::cout << "Parent doSomething" << std::endl;
   }
};

class Child : public Parent {
public:
   int d;
   int e;
   Child(int id, int ie) : Parent(1,2,3) { 
      d = id; e = ie;
   };
   virtual void doSomething(void) {
      std::cout << "Child doSomething : D = " << d << std::endl;
   }
};

void foo(Parent a) {
   a.doSomething();
}

int main(void)
{
   Child c(4, 5);
   foo(c);
   return 0;
}

Đầu ra của chương trình này không phải là một chương trình tương đương trong bất kỳ ngôn ngữ OO lành mạnh nào, bởi vì bạn không thể truyền một đối tượng dẫn xuất theo giá trị cho một hàm mong đợi một đối tượng cơ sở, vì vậy trình biên dịch tạo ra một hàm tạo sao chép ẩn và vượt qua một bản sao của phần Parent của đối tượng Child , thay vì truyền đối tượng Child như bạn bảo nó làm. Các vấn đề ngữ nghĩa ẩn như thế này là lý do tại sao việc truyền các đối tượng theo giá trị nên tránh trong C ++ và hoàn toàn không thể thực hiện được trong hầu hết các ngôn ngữ OO khác.


Điểm rất tốt. Tuy nhiên, tôi tập trung vào các vấn đề trở lại vì làm việc xung quanh chúng mất khá nhiều nỗ lực; chương trình này có thể được sửa chữa bằng việc thêm một ký hiệu duy nhất: void foo (Parent & a)
Ant

OO thực sự không hoạt động ngay mà không có con trỏ
Gustavo Cardoso

-1.000000000000
P Shved

5
Điều quan trọng cần nhớ là Java là pass-by-value (nó chuyển các tham chiếu đối tượng theo giá trị, trong khi các nguyên hàm được truyền hoàn toàn theo giá trị).
Nicole

3
@Pavel Shved - "hai là tốt hơn một!" Hay nói cách khác, thêm dây để treo mình.
Nicole

0

Bởi vì sẽ không có đa hình khác.

Trong Lập trình OO, bạn có thể tạo một Derivedlớp lớn hơn từ một lớp Base, sau đó chuyển nó sang các hàm mong đợi một lớp Base. Khá tầm thường nhỉ?

Ngoại trừ kích thước của đối số của hàm là cố định và được xác định tại thời gian biên dịch. Bạn có thể lập luận tất cả những gì bạn muốn, mã thực thi là như thế này và các ngôn ngữ cần được thực thi tại điểm này hay điểm khác (ngôn ngữ hoàn toàn không bị giới hạn bởi điều này ...)

Bây giờ, có một phần dữ liệu được xác định rõ trên máy tính: địa chỉ của một ô nhớ, thường được biểu thị dưới dạng một hoặc hai "từ". Nó hiển thị dưới dạng con trỏ hoặc tham chiếu trong ngôn ngữ lập trình.

Vì vậy, để truyền các đối tượng có độ dài tùy ý, điều đơn giản nhất cần làm là truyền một con trỏ / tham chiếu đến đối tượng này.

Đây là một hạn chế kỹ thuật của Lập trình OO.

Nhưng vì đối với các loại lớn, bạn thường thích chuyển các tham chiếu hơn để tránh sao chép, nên nó thường không được coi là một cú đánh lớn :)

Mặc dù, có một hệ quả quan trọng, trong Java hoặc C #, khi truyền một đối tượng cho một phương thức, bạn không biết liệu đối tượng của mình sẽ được sửa đổi bởi phương thức đó hay không. Nó làm cho việc gỡ lỗi / song song hóa trở nên khó khăn hơn và đây là vấn đề Ngôn ngữ chức năng và Tài liệu tham khảo xuyên suốt đang cố gắng giải quyết -> sao chép không phải là xấu (khi nó có ý nghĩa).


-1

Câu trả lời là trong tên (gần như dù sao). Một tham chiếu (như một địa chỉ) chỉ đề cập đến một cái gì đó khác, một giá trị là một bản sao khác của một cái gì đó khác. Tôi chắc rằng ai đó có thể đã đề cập đến một cái gì đó cho hiệu ứng sau đây nhưng sẽ có những trường hợp trong đó một và không phải là phù hợp (Bảo mật bộ nhớ so với hiệu quả bộ nhớ). Đó là tất cả về việc quản lý bộ nhớ, bộ nhớ, bộ nhớ ...... NHỚ! : D


-2

Được rồi vì vậy tôi không nói đây chính xác là lý do tại sao các đối tượng là loại tham chiếu hoặc được truyền qua tham chiếu nhưng tôi có thể cho bạn một ví dụ về lý do tại sao đây là một ý tưởng rất tốt trong thời gian dài.

Nếu tôi không nhầm, khi bạn kế thừa một lớp trong C ++, tất cả các phương thức và thuộc tính của lớp đó được sao chép vật lý vào lớp con. Nó sẽ giống như viết nội dung của lớp đó một lần nữa bên trong lớp con.

Vì vậy, điều này có nghĩa là tổng kích thước của dữ liệu trong lớp con của bạn là sự kết hợp của các thứ trong lớp cha và lớp dẫn xuất.

EG: #incolee

class Top 
{   
    int arrTop[20] = {1,2,3,4,5,6,7,8,9,9,8,7,6,5,4,3,2,1};
};  

class Middle : Top 
{   
    int arrMiddle[20] = {1,2,3,4,5,6,7,8,9,9,8,7,6,5,4,3,2,1};
};  

class Bottom : Middle
{   
    int arrBottom[20] = {1,2,3,4,5,6,7,8,9,9,8,7,6,5,4,3,2,1};
};  

int main()
{   
    using namespace std;

    int arr[20];
    cout << "Size of array of 20 ints: " << sizeof(arr) << endl;

    Top top;
    Middle middle;
    Bottom bottom;

    cout << "Size of Top Class: " << sizeof(top) << endl;
    cout << "Size of middle Class: " << sizeof(middle) << endl;
    cout << "Size of bottom Class: " << sizeof(bottom) << endl;

}   

Điều này sẽ cho bạn thấy:

Size of array of 20 ints: 80
Size of Top Class: 80
Size of middle Class: 160
Size of bottom Class: 240

Điều này có nghĩa là nếu bạn có một hệ thống phân cấp lớn của một số lớp, tổng kích thước của đối tượng, như được khai báo ở đây sẽ là sự kết hợp của tất cả các lớp đó. Rõ ràng, những đối tượng này sẽ lớn đáng kể trong rất nhiều trường hợp.

Tôi tin rằng giải pháp là tạo ra nó trên heap và sử dụng các con trỏ. Điều này có nghĩa là kích thước của các đối tượng của các lớp có nhiều phụ huynh sẽ có thể quản lý được, theo một nghĩa nào đó.

Đây là lý do tại sao sử dụng tài liệu tham khảo sẽ là một phương pháp thích hợp hơn để làm điều này.


1
điều này dường như không cung cấp bất cứ điều gì đáng kể qua các điểm được thực hiện và giải thích trong 13 câu trả lời trước
gnat
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.