Có rất nhiều bài viết phàn nàn về quá tải nhà điều hành.
Tôi cảm thấy mình phải làm rõ các khái niệm "quá tải toán tử", đưa ra một quan điểm thay thế cho khái niệm này.
Mã che giấu?
Lập luận này là một ngụy biện.
Obfuscating là có thể trong tất cả các ngôn ngữ ...
Thật dễ dàng để làm xáo trộn mã trong C hoặc Java thông qua các hàm / phương thức như trong C ++ thông qua quá tải toán tử:
// C++
T operator + (const T & a, const T & b) // add ?
{
T c ;
c.value = a.value - b.value ; // subtract !!!
return c ;
}
// Java
static T add (T a, T b) // add ?
{
T c = new T() ;
c.value = a.value - b.value ; // subtract !!!
return c ;
}
/* C */
T add (T a, T b) /* add ? */
{
T c ;
c.value = a.value - b.value ; /* subtract !!! */
return c ;
}
... Ngay cả trong các giao diện chuẩn của Java
Đối với một ví dụ khác, chúng ta hãy xem Cloneable
giao diện trong Java:
Bạn có nghĩa vụ sao chép đối tượng thực hiện giao diện này. Nhưng bạn có thể nói dối. Và tạo ra một đối tượng khác nhau. Trên thực tế, giao diện này yếu đến mức bạn có thể trả lại một loại đối tượng khác hoàn toàn, chỉ để cho vui:
class MySincereHandShake implements Cloneable
{
public Object clone()
{
return new MyVengefulKickInYourHead() ;
}
}
Vì Cloneable
giao diện có thể bị lạm dụng / bị xáo trộn, nên nó có bị cấm trên cùng một lý do quá tải toán tử C ++ không?
Chúng ta có thể quá tải toString()
phương thức của một MyComplexNumber
lớp để nó trả về giờ được xâu chuỗi trong ngày. Nên toString()
quá tải bị cấm, quá? Chúng ta có thể phá hoại MyComplexNumber.equals
để nó trả về một giá trị ngẫu nhiên, sửa đổi các toán hạng ... vv, v.v.
Trong Java, như trong C ++, hoặc bất kỳ ngôn ngữ nào, lập trình viên phải tôn trọng tối thiểu ngữ nghĩa khi viết mã. Điều này có nghĩa là thực hiện một add
chức năng bổ sung và Cloneable
phương thức thực hiện nhân bản và ++
toán tử hơn số gia.
Cái gì đang xáo trộn?
Bây giờ chúng ta biết rằng mã có thể bị phá hoại ngay cả thông qua các phương thức Java nguyên sơ, chúng ta có thể tự hỏi về việc sử dụng thực sự của quá tải toán tử trong C ++ không?
Ký hiệu rõ ràng và tự nhiên: phương pháp so với quá tải toán tử?
Chúng ta sẽ so sánh bên dưới, đối với các trường hợp khác nhau, mã "giống nhau" trong Java và C ++, để có ý tưởng về kiểu mã hóa nào rõ ràng hơn.
So sánh tự nhiên:
// C++ comparison for built-ins and user-defined types
bool isEqual = A == B ;
bool isNotEqual = A != B ;
bool isLesser = A < B ;
bool isLesserOrEqual = A <= B ;
// Java comparison for user-defined types
boolean isEqual = A.equals(B) ;
boolean isNotEqual = ! A.equals(B) ;
boolean isLesser = A.comparesTo(B) < 0 ;
boolean isLesserOrEqual = A.comparesTo(B) <= 0 ;
Xin lưu ý rằng A và B có thể thuộc bất kỳ loại nào trong C ++, miễn là quá tải toán tử được cung cấp. Trong Java, khi A và B không phải là nguyên thủy, mã có thể trở nên rất khó hiểu, ngay cả đối với các đối tượng giống như nguyên thủy (BigInteger, v.v.) ...
Bộ truy cập mảng / container tự nhiên và đăng ký:
// C++ container accessors, more natural
value = myArray[25] ; // subscript operator
value = myVector[25] ; // subscript operator
value = myString[25] ; // subscript operator
value = myMap["25"] ; // subscript operator
myArray[25] = value ; // subscript operator
myVector[25] = value ; // subscript operator
myString[25] = value ; // subscript operator
myMap["25"] = value ; // subscript operator
// Java container accessors, each one has its special notation
value = myArray[25] ; // subscript operator
value = myVector.get(25) ; // method get
value = myString.charAt(25) ; // method charAt
value = myMap.get("25") ; // method get
myArray[25] = value ; // subscript operator
myVector.set(25, value) ; // method set
myMap.put("25", value) ; // method put
Trong Java, chúng ta thấy rằng đối với mỗi container để làm điều tương tự (truy cập nội dung của nó thông qua một chỉ mục hoặc mã định danh), chúng ta có một cách khác nhau để làm điều đó, điều này gây nhầm lẫn.
Trong C ++, mỗi container sử dụng cùng một cách để truy cập nội dung của nó, nhờ quá tải toán tử.
Thao tác tự nhiên tiên tiến
Các ví dụ bên dưới sử dụng một Matrix
đối tượng, được tìm thấy bằng cách sử dụng các liên kết đầu tiên được tìm thấy trên Google cho " đối tượng ma trận Java " và " đối tượng ma trận C ++ ":
// C++ YMatrix matrix implementation on CodeProject
// http://www.codeproject.com/KB/architecture/ymatrix.aspx
// A, B, C, D, E, F are Matrix objects;
E = A * (B / 2) ;
E += (A - B) * (C + D) ;
F = E ; // deep copy of the matrix
// Java JAMA matrix implementation (seriously...)
// http://math.nist.gov/javanumerics/jama/doc/
// A, B, C, D, E, F are Matrix objects;
E = A.times(B.times(0.5)) ;
E.plusEquals(A.minus(B).times(C.plus(D))) ;
F = E.copy() ; // deep copy of the matrix
Và điều này không giới hạn trong ma trận. Các lớp BigInteger
và BigDecimal
Java phải chịu cùng một mức độ khó hiểu, trong khi các tương đương của chúng trong C ++ rõ ràng như các kiểu dựng sẵn.
Lặp đi lặp lại tự nhiên:
// C++ Random Access iterators
++it ; // move to the next item
--it ; // move to the previous item
it += 5 ; // move to the next 5th item (random access)
value = *it ; // gets the value of the current item
*it = 3.1415 ; // sets the value 3.1415 to the current item
(*it).foo() ; // call method foo() of the current item
// Java ListIterator<E> "bi-directional" iterators
value = it.next() ; // move to the next item & return the value
value = it.previous() ; // move to the previous item & return the value
it.set(3.1415) ; // sets the value 3.1415 to the current item
Functor tự nhiên:
// C++ Functors
myFunctorObject("Hello World", 42) ;
// Java Functors ???
myFunctorObject.execute("Hello World", 42) ;
Nối văn bản:
// C++ stream handling (with the << operator)
stringStream << "Hello " << 25 << " World" ;
fileStream << "Hello " << 25 << " World" ;
outputStream << "Hello " << 25 << " World" ;
networkStream << "Hello " << 25 << " World" ;
anythingThatOverloadsShiftOperator << "Hello " << 25 << " World" ;
// Java concatenation
myStringBuffer.append("Hello ").append(25).append(" World") ;
Ok, trong Java bạn cũng có thể sử dụng MyString = "Hello " + 25 + " World" ;
... Nhưng, đợi một chút: Đây là quá tải toán tử, phải không? Không phải là gian lận sao?
: -D
Mã chung?
Các toán hạng sửa đổi mã chung giống nhau phải có thể sử dụng được cho cả các hàm dựng sẵn / nguyên thủy (không có giao diện trong Java), các đối tượng tiêu chuẩn (không thể có giao diện phù hợp) và các đối tượng do người dùng định nghĩa.
Ví dụ: tính giá trị trung bình của hai giá trị của các loại tùy ý:
// C++ primitive/advanced types
template<typename T>
T getAverage(const T & p_lhs, const T & p_rhs)
{
return (p_lhs + p_rhs) / 2 ;
}
int intValue = getAverage(25, 42) ;
double doubleValue = getAverage(25.25, 42.42) ;
complex complexValue = getAverage(cA, cB) ; // cA, cB are complex
Matrix matrixValue = getAverage(mA, mB) ; // mA, mB are Matrix
// Java primitive/advanced types
// It won't really work in Java, even with generics. Sorry.
Thảo luận về quá tải toán tử
Bây giờ chúng ta đã thấy các so sánh công bằng giữa mã C ++ khi sử dụng nạp chồng toán tử và cùng mã trong Java, bây giờ chúng ta có thể thảo luận về "quá tải toán tử" như là một khái niệm.
Quá tải toán tử tồn tại từ trước khi máy tính
Thậm chí bên ngoài của khoa học máy tính, có nhà điều hành quá tải: Ví dụ, trong toán học, các nhà khai thác thích +
, -
, *
, vv được quá tải.
Thật vậy, ý nghĩa của +
, -
, *
vv thay đổi tùy thuộc vào loại của toán hạng (numerics, vectơ, hàm sóng lượng tử, ma trận, vv).
Hầu hết chúng ta, như một phần của các khóa học khoa học, đã học được nhiều ý nghĩa cho các toán tử, tùy thuộc vào các loại toán hạng. Chúng tôi đã tìm thấy chúng khó hiểu, họ?
Quá tải toán tử phụ thuộc vào toán hạng của nó
Đây là phần quan trọng nhất của quá tải toán tử: Giống như trong toán học hoặc trong vật lý, phép toán phụ thuộc vào kiểu toán hạng của nó.
Vì vậy, biết loại toán hạng, và bạn sẽ biết hiệu quả của hoạt động.
Ngay cả C và Java cũng có quá tải toán tử (mã hóa cứng)
Trong C, hành vi thực của toán tử sẽ thay đổi theo toán hạng của nó. Ví dụ: thêm hai số nguyên khác với việc thêm hai số nhân, hoặc thậm chí một số nguyên và một số nguyên. Thậm chí còn có toàn bộ miền số học con trỏ (không cần truyền, bạn có thể thêm vào con trỏ một số nguyên, nhưng bạn không thể thêm hai con trỏ ...).
Trong Java, không có số học con trỏ, nhưng ai đó vẫn tìm thấy nối chuỗi mà không có +
toán tử sẽ đủ lố bịch để biện minh cho một ngoại lệ trong tín hiệu "quá tải toán tử là xấu".
Chỉ là bạn, với tư cách là một lập trình viên C (vì lý do lịch sử) hoặc Java (vì lý do cá nhân , xem bên dưới), bạn không thể cung cấp cho riêng mình.
Trong C ++, quá tải toán tử không phải là tùy chọn ...
Trong C ++, quá tải toán tử cho các kiểu dựng sẵn là không thể (và đây là một điều tốt), nhưng các kiểu do người dùng định nghĩa có thể có quá tải toán tử do người dùng định nghĩa .
Như đã nói trước đó, trong C ++ và ngược lại với Java, các kiểu người dùng không được coi là công dân hạng hai của ngôn ngữ, khi so sánh với các loại tích hợp. Vì vậy, nếu các loại tích hợp có toán tử, các loại người dùng cũng có thể có chúng.
Sự thật là, như toString()
, clone()
, equals()
phương pháp này cho Java ( tức là bán tiêu chuẩn như ), C ++ nhà khai thác quá tải là rất nhiều một phần của C ++ mà nó trở nên tự nhiên như các nhà khai thác C ban đầu, hoặc trước khi phương pháp Java nêu.
Kết hợp với lập trình mẫu, quá tải toán tử trở thành một mẫu thiết kế nổi tiếng. Trong thực tế, bạn không thể đi rất xa trong STL mà không sử dụng các toán tử quá tải và các toán tử quá tải cho lớp của riêng bạn.
... nhưng nó không nên bị lạm dụng
Quá tải toán tử nên cố gắng tôn trọng ngữ nghĩa của toán tử. Không được trừ trong một +
toán tử (như trong "không trừ trong một add
hàm" hoặc "trả về crap trong một clone
phương thức").
Quá tải diễn viên có thể rất nguy hiểm vì chúng có thể dẫn đến sự mơ hồ. Vì vậy, họ thực sự nên được dành riêng cho các trường hợp được xác định rõ. Đối với &&
và ||
, đừng bao giờ làm quá tải chúng trừ khi bạn thực sự biết những gì bạn đang làm, vì bạn sẽ mất đánh giá ngắn mạch mà các nhà khai thác bản địa &&
và ||
thích thú.
Vậy ... Ok ... Vậy tại sao Java không thể?
Bởi vì James Gosling đã nói như vậy:
Tôi đã bỏ quá tải toán tử như một lựa chọn khá cá nhân vì tôi đã thấy quá nhiều người lạm dụng nó trong C ++.
James Gosling. Nguồn: http://www.gotw.ca/publications/c_family_interview.htmlm
Vui lòng so sánh văn bản của Gosling ở trên với Stroustrup dưới đây:
Nhiều quyết định thiết kế C ++ bắt nguồn từ việc tôi không thích ép buộc mọi người thực hiện mọi thứ theo một cách đặc biệt nào đó [...] Thông thường, tôi bị cám dỗ ngoài vòng pháp luật một tính năng mà cá nhân tôi không thích, tôi đã từ chối vì tôi không nghĩ rằng mình đã làm vậy quyền buộc quan điểm của tôi về người khác .
Bjarne Stroustrup. Nguồn: Thiết kế và tiến hóa của C ++ (Nền tảng chung 1.3)
Toán tử quá tải sẽ có lợi cho Java?
Một số đối tượng sẽ được hưởng lợi rất nhiều từ quá tải toán tử (các loại cụ thể hoặc số, như BigDecimal, số phức, ma trận, container, iterator, bộ so sánh, trình phân tích cú pháp, v.v.).
Trong C ++, bạn có thể kiếm lợi từ lợi ích này nhờ sự khiêm tốn của Stroustrup. Trong Java, bạn chỉ đơn giản là say sưa vì lựa chọn cá nhân của Gosling .
Nó có thể được thêm vào Java không?
Các lý do cho việc không thêm quá tải toán tử hiện tại trong Java có thể là sự pha trộn của chính trị nội bộ, dị ứng với tính năng, mất lòng tin của các nhà phát triển (bạn biết, những kẻ phá hoại dường như ám ảnh các nhóm Java ...), khả năng tương thích với các JVM trước đó, thời gian để viết một đặc điểm kỹ thuật chính xác, vv ..
Vì vậy, đừng nín thở chờ đợi tính năng này ...
Nhưng họ làm điều đó trong C # !!!
Ừ ...
Mặc dù đây không phải là sự khác biệt duy nhất giữa hai ngôn ngữ, nhưng ngôn ngữ này không bao giờ làm tôi buồn cười.
Rõ ràng, những người C #, với "mọi nguyên thủy là một struct
, và struct
xuất phát từ Object" , đã thử ngay từ lần thử đầu tiên.
Bất chấp tất cả FUD chống lại quá tải toán tử được xác định đã sử dụng, các ngôn ngữ sau hỗ trợ nó: Scala , Dart , Python , F # , C # , D , Algol 68 , Smalltalk , Groovy , Perl 6 , C ++, Ruby , Haskell , MATLAB , Eiffel , Lua , Clojure , Fortran 90 , Swift , Ada , Delphi 2005 ...
Rất nhiều ngôn ngữ, với rất nhiều triết lý khác nhau (và đôi khi đối lập), và tất cả chúng đều đồng ý về điểm đó.
Thức ăn cho suy nghĩ ...