Những người thao tác iomanip nào là 'dính'?


140

Gần đây tôi đã gặp sự cố khi tạo stringstreamdo thực tế là tôi giả định không chính xác std::setw()sẽ ảnh hưởng đến chuỗi chuỗi cho mỗi lần chèn, cho đến khi tôi thay đổi rõ ràng. Tuy nhiên, nó luôn luôn không được đặt sau khi chèn.

// With timestruct with value of 'Oct 7 9:04 AM'
std::stringstream ss;
ss.fill('0'); ss.setf(ios::right, ios::adjustfield);
ss << setw(2) << timestruct.tm_mday;
ss << timestruct.tm_hour;
ss << timestruct.tm_min;
std::string filingTime = ss.str(); // BAD: '0794'

Vì vậy, tôi có một số câu hỏi:

  • Tại sao setw()lại theo cách này?
  • Có bất kỳ thao tác khác theo cách này?
  • Có sự khác biệt trong hành vi giữa std::ios_base::width()std::setw()?
  • Cuối cùng có một tài liệu tham khảo trực tuyến ghi lại rõ ràng hành vi này? Tài liệu về nhà cung cấp của tôi (MS Visual Studio 2005) dường như không thể hiện rõ điều này.

Một vòng làm việc ở đây: stackoverflow.com/a/37495361/984471
Manohar Reddy Poreddy

Câu trả lời:


87

Ghi chú quan trọng từ các ý kiến ​​dưới đây:

Bởi Martin:

@Charele: Sau đó, theo yêu cầu này, tất cả các thao tác đều dính. Ngoại trừ setw mà dường như được thiết lập lại sau khi sử dụng.

Charles:

Chính xác! và lý do duy nhất mà setw dường như hành xử khác đi là bởi vì có các yêu cầu đối với các hoạt động đầu ra được định dạng để rõ ràng. Băng thông (0) luồng đầu ra.

Sau đây là cuộc thảo luận dẫn đến kết luận trên:


Nhìn vào mã, các trình điều khiển sau đây trả về một đối tượng chứ không phải là một luồng:

setiosflags
resetiosflags
setbase
setfill
setprecision
setw

Đây là một kỹ thuật phổ biến để áp dụng một thao tác cho chỉ đối tượng tiếp theo được áp dụng cho luồng. Thật không may, điều này không ngăn cản họ bị dính. Các xét nghiệm chỉ ra rằng tất cả chúng ngoại trừ setwlà dính.

setiosflags:  Sticky
resetiosflags:Sticky
setbase:      Sticky
setfill:      Sticky
setprecision: Sticky

Tất cả các thao tác khác trả về một đối tượng luồng. Do đó, bất kỳ thông tin trạng thái nào chúng thay đổi phải được ghi lại trong đối tượng luồng và do đó là vĩnh viễn (cho đến khi một trình thao tác khác thay đổi trạng thái). Do đó, các trình thao tác sau phải là Trình thao tác dính .

[no]boolalpha
[no]showbase
[no]showpoint
[no]showpos
[no]skipws
[no]unitbuf
[no]uppercase

dec/ hex/ oct

fixed/ scientific

internal/ left/ right

Các trình điều khiển này thực sự thực hiện một thao tác trên chính luồng chứ không phải đối tượng luồng (Mặc dù về mặt kỹ thuật, luồng là một phần của trạng thái đối tượng luồng). Nhưng tôi không tin rằng chúng ảnh hưởng đến bất kỳ phần nào khác của trạng thái đối tượng luồng.

ws/ endl/ ends/ flush

Kết luận là setw dường như là trình thao tác duy nhất trên phiên bản của tôi không dính.

Đối với Charles, một mẹo đơn giản chỉ ảnh hưởng đến mục tiếp theo trong chuỗi:
Dưới đây là một ví dụ về cách một đối tượng có thể được sử dụng để thay đổi trạng thái sau đó thay đổi trạng thái sau đó đưa nó trở lại bằng cách sử dụng một đối tượng:

#include <iostream>
#include <iomanip>

// Private object constructed by the format object PutSquareBracket
struct SquareBracktAroundNextItem
{
    SquareBracktAroundNextItem(std::ostream& str)
        :m_str(str)
    {}
    std::ostream& m_str;
};

// New Format Object
struct PutSquareBracket
{};

// Format object passed to stream.
// All it does is return an object that can maintain state away from the
// stream object (so that it is not STICKY)
SquareBracktAroundNextItem operator<<(std::ostream& str,PutSquareBracket const& data)
{
    return SquareBracktAroundNextItem(str);
}

// The Non Sticky formatting.
// Here we temporariy set formating to fixed with a precision of 10.
// After the next value is printed we return the stream to the original state
// Then return the stream for normal processing.
template<typename T>
std::ostream& operator<<(SquareBracktAroundNextItem const& bracket,T const& data)
{
    std::ios_base::fmtflags flags               = bracket.m_str.flags();
    std::streamsize         currentPrecision    = bracket.m_str.precision();

    bracket.m_str << '[' << std::fixed << std::setprecision(10) << data << std::setprecision(currentPrecision) << ']';

    bracket.m_str.flags(flags);

    return bracket.m_str;
}


int main()
{

    std::cout << 5.34 << "\n"                        // Before 
              << PutSquareBracket() << 5.34 << "\n"  // Temp change settings.
              << 5.34 << "\n";                       // After
}


> ./a.out 
5.34
[5.3400000000]
5.34

Tấm cheat đẹp. Thêm một tham chiếu đến nơi thông tin đến từ đâu, và nó sẽ là một câu trả lời hoàn hảo.
Đánh dấu tiền chuộc

1
Tuy nhiên tôi có thể xác minh rằng setfill () trên thực tế là 'dính' mặc dù nó trả về một đối tượng. Vì vậy, tôi nghĩ rằng câu trả lời này là không chính xác.
John K

2
Các đối tượng trả về một luồng Phải dính, trong khi các đối tượng trả về một đối tượng có thể bị dính nhưng không bắt buộc. Tôi sẽ cập nhật câu trả lời với Thông tin của John.
Martin York

1
Tôi không chắc rằng tôi hiểu lý do của bạn. Tất cả các trình điều khiển lấy tham số được triển khai dưới dạng các hàm miễn phí trả về một đối tượng không xác định hoạt động trên luồng khi đối tượng đó được chèn vào luồng vì đây là cách duy nhất (?) Để duy trì cú pháp chèn với tham số. Dù bằng cách nào, thích hợp operator<<cho trình thao tác đảm bảo rằng trạng thái của luồng được thay đổi theo một cách nhất định. Không hình thức nào thiết lập bất kỳ loại lính gác nhà nước. Đây chỉ là hành vi của hoạt động chèn được định dạng tiếp theo xác định phần nào của trạng thái được đặt lại nếu có.
CB Bailey

3
Chính xác! và lý do duy nhất có setwvẻ hành xử khác nhau là vì có các yêu cầu đối với các hoạt động đầu ra được định dạng để rõ ràng .width(0)luồng đầu ra.
CB Bailey

31

Lý do dường widthnhư không 'dính' là các hoạt động nhất định được đảm bảo để gọi .width(0)một luồng đầu ra. Những người đang có:

21.3.7.9 [lib.opes.io]:

template<class charT, class traits, class Allocator>
  basic_ostream<charT, traits>&
    operator<<(basic_ostream<charT, traits>& os,
               const basic_string<charT,traits,Allocator>& str);

22.2.2.2.2 [lib.facet.num.put.virtuals]: Tất cả do_putquá tải cho num_putmẫu. Chúng được sử dụng bởi quá tải operator<<lấy một basic_ostreamvà một kiểu số.

22.2.6.2.2 [lib.locale.money.put.virtuals]: Tất cả do_putquá tải cho money_putmẫu.

27.6.2.5.4 [lib.ostream.inserters.character]: Quá tải operator<<khi lấy một basic_ostreamvà một trong các loại char của khởi tạo basic_ostream hoặc char, đã ký charhoặc unsigned charhoặc con trỏ tới mảng của các loại char này.

Thành thật mà nói tôi không chắc chắn về lý do cho việc này, nhưng không có trạng thái nào khác ostreamcần được thiết lập lại bằng các hàm đầu ra được định dạng. Tất nhiên, những thứ như badbitfailbitcó thể được đặt nếu có lỗi trong hoạt động đầu ra, nhưng điều đó nên được dự kiến.

Lý do duy nhất mà tôi có thể nghĩ đến để đặt lại chiều rộng là có thể gây ngạc nhiên nếu, khi cố gắng xuất một số trường được phân tách, các dấu phân cách của bạn đã được đệm.

Ví dụ

std::cout << std::setw(6) << 4.5 << '|' << 3.6 << '\n';

"   4.5     |   3.6      \n"

Để 'sửa' điều này sẽ mất:

std::cout << std::setw(6) << 4.5 << std::setw(0) << '|' << std::setw(6) << 3.6 << std::setw(0) << '\n';

trong khi với chiều rộng đặt lại, đầu ra mong muốn có thể được tạo với ngắn hơn:

std::cout << std::setw(6) << 4.5 << '|' << std::setw(6) << 3.6 << '\n';

6

setw()chỉ ảnh hưởng đến việc chèn tiếp theo. Đó chỉ là cách setw()cư xử. Các hành vi của setw()là giống như ios_base::width(). Tôi đã nhận được setw()thông tin của tôi từ cplusplus.com .

Bạn có thể tìm thấy một danh sách đầy đủ các trình thao tác ở đây . Từ liên kết đó, tất cả các cờ luồng sẽ được đặt cho đến khi được thay đổi bởi một trình thao tác khác. Một lưu ý về left, rightinternalthao tác: Họ giống như những lá cờ khác và làm kiên trì cho đến khi thay đổi. Tuy nhiên, chúng chỉ có hiệu ứng khi độ rộng của luồng được đặt và độ rộng phải được đặt cho mỗi dòng. Ví dụ

cout.width(6);
cout << right << "a" << endl;
cout.width(6);
cout << "b" << endl;
cout.width(6);
cout << "c" << endl;

sẽ cung cấp cho bạn

>     a
>     b
>     c

nhưng

cout.width(6);
cout << right << "a" << endl;
cout << "b" << endl;
cout << "c" << endl;

sẽ cung cấp cho bạn

>     a
>b
>c

Các trình thao tác Đầu vào và Đầu ra không bị dính và chỉ xảy ra một lần khi chúng được sử dụng. Các trình thao tác được tham số hóa là khác nhau, đây là một mô tả ngắn gọn về mỗi:

setiosflagscho phép bạn đặt cờ theo cách thủ công, một danh sách có thể được hiển thị ở đây , vì vậy nó sẽ bị dính.

resetiosflagshành xử tương tự như setiosflagsngoại trừ nó hủy bỏ các cờ được chỉ định.

setbase đặt cơ sở của số nguyên được chèn vào luồng (vì vậy 17 trong cơ sở 16 sẽ là "11" và trong cơ sở 2 sẽ là "10001").

setfillđặt ký tự điền để chèn vào luồng khi setwđược sử dụng.

setprecision đặt độ chính xác thập phân được sử dụng khi chèn giá trị dấu phẩy động.

setw chỉ thực hiện chèn tiếp theo chiều rộng được chỉ định bằng cách điền vào ký tự được chỉ định trong setfill


Chà, hầu hết trong số họ chỉ là cài đặt cờ, vì vậy những cái đó là "dính". setw () dường như là người duy nhất ảnh hưởng đến chỉ một lần chèn. Bạn có thể tìm hiểu chi tiết cụ thể hơn cho từng loại tại cplusplus.com/reference/iostream/manipulators
David Brown

Vâng std::hexcũng không dính và rõ ràng, std::flushhoặc std::setiosflagscũng không dính. Vì vậy, tôi không nghĩ rằng nó đơn giản.
sbi

Chỉ cần thử nghiệm hex và setiosflags (), cả hai đều có vẻ dính (cả hai chỉ đơn giản là đặt cờ tồn tại cho luồng đó cho đến khi bạn thay đổi chúng).
David Brown

Vâng, trang web tuyên bố std::hexkhông dính là sai - tôi cũng chỉ tìm thấy điều này. Tuy nhiên, các luồng phát trực tuyến có thể thay đổi ngay cả khi bạn không chèn std::setiosflagslại, do đó người ta có thể thấy điều này là không dính. Ngoài ra, std::wscũng không dính. Vì vậy, nó không dễ dàng.
sbi

Bạn đã nỗ lực khá nhiều để cải thiện câu trả lời của bạn. +1
sbi
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.