2012-10-17 36 views
5

thể trùng lặp:
Operator overloadingLàm thế nào để các nhà khai thác "<<" and ">>" làm I/O?

Tôi đang làm cho một sự trở lại đáng được chờ đợi để C++ và có một số ký hiệu cơ bản mà không thực sự dường như đó nổi bật trong các ngôn ngữ khác.

Nếu bạn nhìn vào dòng mã này

cout << "firstvalue is " << firstvalue << endl; 

Tôi nhận ra này không. "Firstvalue là x" của nó ghi vào bàn điều khiển. x là giá trị của giá trị đầu tiên. Tuy nhiên, tôi không biết bất cứ điều gì về "< <" hoặc ">>" dấu ngoặc nhọn đôi. Tôi đã không thể nghiên cứu họ hoặc những gì họ làm vì tôi không biết tên chính thức cho họ.

Câu hỏi của tôi là, những gì thực sự xảy ra (từng bước) trong tuyên bố ở trên? Và những "< <" này là gì? Tôi nghĩ rằng tôi hiểu rằng cout là một chức năng thư viện chuẩn để ghi vào bàn điều khiển. Tuy nhiên tôi được sử dụng để ghi chú mục tiêu-c hoặc dấu chấm. Tôi không thấy những gì đối tượng này "cout" chức năng là một thành viên của.

Tôi có thể hiểu printf dễ dàng hơn một chút, vì ít nhất nó cung cấp niềng răng cho các đối số. ví dụ. printf ("chuỗi của bạn ở đây").

+2

Đọc bắt buộc: [Hướng dẫn sách và danh sách sách chính xác C++] (http://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list) –

+1

Trong ngữ cảnh này, '<< 'là một [toán tử] (http://en.cppreference.com/w/cpp/io/basic_ostream/operator_ltlt) và biểu thức là kết quả của các cuộc gọi đến toán tử đó. – juanchopanza

+2

'cout' không phải là một hàm. Nó là một đối tượng, mà toán tử '<<' bị quá tải. – Grizzly

Trả lời

13

C++ cho phép nhà khai thác quá tải. Điều đó có nghĩa là một kiểu do người dùng định nghĩa có thể định nghĩa hành vi của chính nó trên các toán tử dựng sẵn. Trong trường hợp này các nhà khai thác này được gọi là: left shift hay right shift nhà khai thác. Các toán tử này thường được sử dụng để dịch chuyển bit, nhưng thư viện chuẩn sử dụng chúng để tượng trưng cho các hoạt động truyền trực tuyến.

Bạn có thể tìm danh sách các toán tử hiện có trong C and C++ here.

Trong trường hợp của bạn, bạn đang phát trực tuyến chuỗi chữ và giá trị của một số loại thành std::cout, là đối tượng thuộc loại std::basic_ostream.

Step-by-Step

Sau khi quy tắc ưu tiên đã được áp dụng mã của bạn trông như thế này:

((cout << "foobar") << x) << endl; 

Trình biên dịch sẽ cơ bản chuyển đổi object << object biểu thành lời gọi hàm.

operator<<(operator<<(operator<<(cout, "foobar"), x), endl); 

Sau đó, nó sẽ tìm ra tình trạng quá tải cần gọi. (Điều này thực sự là khó khăn. Hiện tại, nó đủ để tin rằng nó chỉ đơn giản là tìm kiếm một sự quá tải của operator<< với các đối số phù hợp).

Hầu hết các quá tải được tích hợp cho basic_ostream là herehere.

+0

Quá tải mà lấy chuỗi chữ không thực sự là một thành viên, đó là một trong những chức năng miễn phí mà tôi đã liên kết. –

+0

Cảm ơn, tôi đã không hiểu điều này lúc đầu, sau đó tôi đã dành một chút thời gian để đọc Accelerated C++. Và sau khi đọc lại điều này, tất cả đều có ý nghĩa hơn. –

1

Đó là đường cú pháp sau:

// Let the function 'print' be a renaming of 'operator<<' 
// with T being the type of the object you want to print. 
std::ostream& print(std::ostream&, const T&); 

// 1) Print "first value is" and then return the stream you 
// to which to just printed (ie. cout). 2) Use the returned 
// stream to chain function calls and print 'firstValue'. 
print(print(std::cout, "first value is"), firstValue); 
2

Họ được gọi là chèn dòng (hoặc khai thác, trong trường hợp của istream >>), và thực sự là một tình trạng quá tải ngữ nghĩa của trái ca và đúng -nhà khai thác nhanh.

Vì vậy, đây:

int x = 1 << 1; 

là một sự thay đổi chút, nhưng điều này:

std::cout << x; 

là chèn dòng. Bạn có thể viết rõ ràng là:

operator <<(std::cout, x); 

và nhận được kết quả tương tự. Định dạng thông thường của các nhà khai thác chèn dòng (họ có thể bị quá tải với nhiều loại người dùng định nghĩa, vì vậy nó không phải bất thường để viết riêng của bạn) là

std::ostream& operator <<(std::ostream&, T value); 

các dòng sản lượng được trả lại (bằng cách tham khảo) để bạn có thể chuỗi các cuộc gọi: Ví dụ bạn dịch là:

operator<< (
    operator<< (
    operator<<(std::cout, "firstvalue"), 
    firstvalue 
), 
    std::endl 
); 

Oh, và ... std::cout (và std::cerr vv) là không chức năng: họ là đối tượng toàn cầu. Chức năng ở đây là toán tử quá tải <<. Hãy nghĩ về chúng như là FILE *stdout, *stderr tương đương.

Có một số ưu điểm của C++ iostreams trên printf et. al:

  • loại an toàn: bạn không thể nhầm lẫn in một số nguyên với "%f" và nhận rác, bởi vì độ phân giải quá tải tự động chọn std::ostream& operator<<(std::ostream&, double) chức năng tại thời gian biên dịch
  • hỗ trợ cho các kiểu do người dùng định nghĩa: bạn có thể viết một toán tử chèn luồng cho lớp mới của bạn và nó sẽ hoạt động, ở mọi nơi
  • trừu tượng luồng: bạn có thể sử dụng cùng một quá tải (vì vậy bạn chỉ viết chúng một lần) để định dạng thành stdout và stderr (cout/cerr) và tệp (std::ofstream) và các chuỗi (std::ostringstream). Không cần phải xử lý riêng biệt printf/fprintf/snprintf.

Ngoài ra còn có một số nhược điểm:

  • hiệu suất: có một số hình phạt cho tất cả trừu tượng đó, và tính tổng quát của hệ thống locale mà được cấu hình trong thời gian chạy
  • verbosity: ít nhất là cho nguyên thủy các loại đã được hỗ trợ bởi printf, các chuỗi định dạng có nhiều kích thước hơn và rõ ràng hơn
4

Toán tử << là "số học dịch trái" trong C + +.Ví dụ:

3 << 2 

đánh giá đến 12. Nguyên nhân là do các đại diện nhị phân của 3 là

00000011 

chuyển nó hai lần sang trái bạn sẽ có được

00001100 

và giá trị số của kết quả là 12.

Nó phải làm gì với đầu ra? Không có gì cả. Tuy nhiên trong C++ bạn có thể xác định lại ý nghĩa của các toán tử nhờ quá tải. Thư viện chuẩn C++ đã quyết định xác định lại ý nghĩa của toán tử dịch chuyển trái như một kiểu "gửi-tới-stream".

Vì vậy, những gì xảy ra là

std::cout << "whatever" 

lợi nhuận là giá trị std::cout, nhưng như tác dụng phụ nó ra chuỗi "bất cứ điều gì".

Toán tử được chọn vì nó có mức ưu tiên hợp lý (quá tải không thể thay đổi ưu tiên và bạn không thể xác định toán tử mới) và hình dạng làm cho nó trông hơi "tự nhiên". Tuy nhiên lưu ý rằng các nhà điều hành trái ca chỉ là một nhà điều hành bình thường và ví dụ không có đảm bảo về trật tự thẩm định:

std::cout << f() << g() << h(); 

đây đầu ra sẽ là kết quả của việc gọi f(), tiếp theo là kết quả của việc gọi g() và theo sau là kết quả của việc gọi h() ... nhưng bản thân các hàm có thể được gọi theo thứ tự khác và ví dụ h() có thể được gọi trước!

Vì vậy, theo nghĩa "trình tự chuỗi" của toán tử là gây hiểu lầm, vì đó là về chuỗi đầu ra, nhưng không phải về chuỗi đánh giá.

2

<< là nhà điều hành, giống như cách + là nhà điều hành và * là nhà điều hành. Theo cách sau đây là biểu thức tương đương:

5 + 3 + 2 
((5 + 3) + 2) 

Vậy là hai tiếp theo:

std::cout << "Hello" << std::endl 
((std::cout << "Hello") << std::endl) 

Nó chỉ là một nhà điều hành với hai toán hạng. Đối với các loại cơ bản, các toán tử <<>> được gọi là toán tử dịch trái và phải. Họ thực hiện dịch chuyển bit. Ví dụ: sẽ chuyển tất cả các bit ở 5 (0101) sang một vị trí để nhận 10 (1010).

Tuy nhiên, giống như hầu hết các nhà khai thác khác, bạn có thể quá tải các toán tử thay đổi. Trong trường hợp của thư viện đầu vào/đầu ra, toán tử shift bị quá tải để cung cấp cú pháp tự nhiên cho đầu vào và đầu ra cho một luồng. Đó là vì hướng của các mã thông báo <<>> trông giống như một thứ đang chảy theo cách này hay cách khác. Với các lớp I/O này, các toán tử quá tải này trả về một tham chiếu đến luồng mà bạn đang thực hiện toán tử để chúng có thể được xích lại với nhau.

Bạn có thể quá tải toán tử thay đổi cho một lớp cụ thể bằng cách cung cấp hàm thành viên operator<< hoặc operator>> có một đối số (toán hạng ở bên phải toán tử). Ngoài ra, bạn có thể cung cấp chức năng không phải là thành viên với cùng tên lấy hai đối số, hai toán hạng của toán tử tương ứng.

Các vấn đề liên quan