2011-08-30 39 views
6

Tôi có giao diện lớp sau:Công việc xếp tầng "này" hoạt động như thế nào?

class Time 
    { 
    public: 
    Time(int = 0, int = 0, int = 0); 
    Time &setHour(int);     
    Time &setMinute(int);    
    Time &setSecond(int); 

    private: 
    int hour; 
    int minute; 
    int second; 
    }; 

Việc thực hiện là ở đây:

Time &Time::setHour(int h) 
    { 
    hour = (h >= 0 && h < 24) ? h : 0; 
    return *this; 
    } 


    Time &Time::setMinute(int m) 
    { 
    minute = (m >= 0 && m < 60) ? m : 0; 
    return *this; 
    } 


    Time &Time::setSecond(int s) 
    { 
    second = (s >= 0 && s < 60) ? s : 0; 
    return *this; 
    } 

Trong tập tin cpp chính tôi, tôi có mã này:

int main() 
{ 
    Time t;  
    t.setHour(18).setMinute(30).setSecond(22); 
    return 0; 
} 

Làm thế nào là nó có thể kết nối các cuộc gọi chức năng này với nhau? Tôi không hiểu tại sao điều này lại hiệu quả.

Trả lời

5

Mỗi phương pháp của t trả về một tham chiếu tới t. Tham chiếu là bí danh. Vì vậy, nếu bạn đã làm

Time t; 
Time& tAgain = t; 
tAgain.setMinute(30); 

tAgain.setMinute cũng làm thay đổi thời gian của t.

Bây giờ ngoại suy ví dụ đơn giản đó để xếp tầng. Mỗi phương pháp của t trả về một tham chiếu đến bản thân

Time &Time::setSecond(int s) 
    { 
     second = (s >= 0 && s < 60) ? s : 0; 
     return *this; 
    } 

Vì vậy, trong biểu thức:

t.setHour(18).setMinute(30) 

t.setHour(18) cuộc gọi setHour trên t, sau đó trả về một tham chiếu đến t. Trong trường hợp này tham chiếu là tạm thời. Vì vậy, bạn có thể nghĩ về nó như thể dòng trên đã thay đổi như sau về đánh giá setHour:

tAgain.setMinute(30); 

t.setHour trở một tài liệu tham khảo - tương tự như tAgain của chúng tôi ở trên. Chỉ cần một bí danh cho chính nó.

5

Do thực tế là mỗi hàm trả về một tham chiếu đến đối tượng đối tượng này (Trả về * điều này).

Về cơ bản, điều này có nghĩa là mỗi khi hàm được gọi là thay đổi có liên quan và sau đó chuyển toàn bộ đối tượng trở lại làm tham chiếu. Sau đó, có thể thực hiện cuộc gọi trên đối tượng được trả về đó.

Nó cũng có thể được viết như sau:

Time t; 
Time& t1 = t.setHour(18); // t1 will refer to the same object as t. 
Time& t2 = t1.setMinute(30); // t2 will refer to the same object as t1 and t. 
Time& t3 = t2.setSecond(22); // t3 will refer to the same object as t2, t1 and t. 

Điều đó có thể làm cho nó dễ dàng hơn để hiểu những gì đang xảy ra.

+0

oh tôi hiểu rồi ... 't.setHour (18)' sẽ bỏ '(* this)' sẽ được sử dụng để tham chiếu hàm kế tiếp ... – teacher

+1

@teacher: Chính xác. – Goz

14

Lý do mà các công trình này một cách chính xác là khi bạn gọi

t.setHour(18) 

Giá trị trả về là một Time&, một tham chiếu đến một đối tượng Time. Quan trọng hơn, nó được định nghĩa như

Time &Time::setHour(int h) 
{ 
    hour = (h >= 0 && h < 24) ? h : 0; 
    return *this; // <--- right here 
} 

Bên trong của một hàm thành viên, this là một con trỏ đến đối tượng mà trên đó các cuộc gọi được thực hiện, và *this là một tham chiếu đến đối tượng mà trên đó các cuộc gọi được thực hiện (các đối tượng thu). Điều này có nghĩa là khi bạn gọi setHour, hàm đặt giờ vào thời gian, sau đó trả về tham chiếu đến đối tượng Time mà bạn đã thực hiện cuộc gọi. Vì vậy, t.setHour(18) cả hai đặt giờ và sau đó trả về một tham chiếu đến đối tượng người nhận. Bằng cách đó, bạn có thể viết

t.setHour(18).setMinute(30).setSecond(22); 

vì nó hiểu là

((t.setHour(18)).setMinute(30)).setSecond(22); 

và trong từng trường hợp hàm trả về một tham chiếu đến t.

Thông thường, bất kỳ khi nào hàm trả về tham chiếu và tham chiếu đó là *this, mọi thao tác bạn thực hiện trên giá trị trả về của hàm sẽ không thể phân biệt được với các thao tác bạn thực hiện trên chính đối tượng đó.

Hy vọng điều này sẽ hữu ích!

3

Điều này tương tự như các nhà khai thác luồng quá tải.

ostream& operator<<(ostream& s, const T& val) 
{ 
    s << val; 
    return s; 
} 

Bạn làm điều này vì bạn sửa đổi luồng và trả lại luồng để có thể sử dụng trong cuộc gọi tầng tiếp theo nếu muốn. Nó tiếp tục được truyền qua tham chiếu để nó có thể tiếp tục đi vào phân đoạn biểu thức tiếp theo.

Thats cách:

std::cerr << 1 << 2 << 3 << std::endl; 

công trình! :)

+0

Điều này có vẻ giống như một bình luận cho tôi – sehe

1

Kỹ thuật này được gọi là method chaining. Trong ví dụ bạn đã đưa ra, tất cả các phương thức trả về cùng một đối tượng (this), vì vậy tất cả chúng đều ảnh hưởng đến cùng một đối tượng. Điều đó không phổ biến, nhưng nó rất hữu ích để biết rằng nó không phải là trường hợp; một số hoặc tất cả các phương thức trong chuỗi có thể trả về các đối tượng khác nhau. Ví dụ, bạn cũng có thể có các phương pháp như:

Date Time::date() const; 
String Date::dayOfWeek() const; 

trong trường hợp này bạn có thể nói:

Time t; 
String day = t.date().dayOfWeek(); 

để có được tên của ngày trong tuần. Trong trường hợp đó, t.date() trả về đối tượng Ngày được sử dụng lần lượt để gọi dayOfWeek().

+0

+1 chỉ cho cụm từ đầu tiên, -1 vì không nói 'Ngày giờ :: ngày() const;' và sử dụng một khoảng trống dư thừa. Do đó, + -0. chỉnh sửa: Sau khi chỉnh sửa câu trả lời của bạn, tôi sẽ +1 nó. –

+0

@phresnel, Đó là một cách để làm điều đó, nhưng khác với những gì tôi dự định. Tại sao trả lại một đối tượng mới thay vì tham chiếu đến đối tượng hiện tại? Trả về một tham chiếu đến một đối tượng hiện có có vẻ không kém phần hợp lệ, như được minh họa [ở đây] (http://en.wikipedia.org/wiki/Method_chaining) và [ở đây] (http://www.parashift.com/c++-faq- lite/ctors.html # faq-10.20). Nó cũng có vẻ hiệu quả hơn và tự nhiên hơn khi bạn cho rằng chuỗi thường được sử dụng để cấu hình một đối tượng mới. – Caleb

+0

Được rồi, tôi nghĩ đó là lỗi chính tả. Bạn có chắc chắn 'dayOfWeek()' phải trả về một tham chiếu đến một chuỗi không phải là const không? Tại sao dư thừa 'void'? Ngoài ra, những ví dụ đó trả về '* this', và không phải là trường hợp của một loại khác, mà có thể rất tinh tế giới thiệu các lỗi nguy hiểm của loại sắp xảy ra trước khách hàng của bạn. –

1

Nó có thể hữu ích nếu bạn nghĩ rằng các câu lệnh được giải quyết từng bước một.

Lấy sau ví dụ:

x = 1 + 2 * 3 - 4; 
x = 1 + 6 - 4; 
x = 7 - 4; 
x = 3; 

C++ cũng làm như vậy với các cuộc gọi chức năng và mọi thứ khác bạn làm trong một tuyên bố, giải quyết từng yếu tố bên trong thứ tự ưu tiên điều hành. Vì vậy, bạn có thể nghĩ đến ví dụ của bạn như đang được giải quyết trong cùng một cách:

t.setHour(18).setMinute(30).setSecond(22); 
t.setMinute(30).setSecond(22); // hour is now set to 18 
t.setSecond(22); // minute is now set to 30 
t; // seconds now set to 22 

Nếu bạn trở this thay vì *this, và do đó trở về con trỏ thay vì tài liệu tham khảo, bạn sẽ nhận được tác dụng tương tự ngoại trừ bạn sẽ thay thế . với -> (giống như một ví dụ, bạn đang làm nó ngay bằng cách sử dụng tài liệu tham khảo). Trong cùng một cách, nếu bạn trả lại một con trỏ hoặc tham chiếu đến một đối tượng khác nhau, bạn có thể làm điều tương tự với điều đó. Ví dụ: giả sử bạn có hàm trả về đối tượng Time.

class Time{ 
    public: 
    int getSeconds(){ 
     return seconds; 
    }; 
    int seconds; 
}; 

Time getCurrentTime(){ 
    Time time = doSomethingThatGetsTheTime(); 
    return time; 
}; 

int seconds = getCurrentTime().getSeconds();  

Bạn nhận được giây mà không phải chia tuyên bố thành hai câu lệnh khác nhau hoặc tạo biến tạm thời để giữ đối tượng Thời gian trả về.

Câu hỏi này C++: Using '.' operator on expressions and function calls có thêm một chút nữa nếu bạn muốn đọc.

1

vì khi một hàm được thực hiện và trả về thì nó sẽ trả về tham chiếu đến chính nó để nó có thể gọi functions.

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