如何直接从输入流中插入值?
Posted
技术标签:
【中文标题】如何直接从输入流中插入值?【英文标题】:How to insert values in set directly from input stream? 【发布时间】:2021-12-22 07:51:27 【问题描述】:如何将输入从输入流直接插入到集合容器中?
这是我需要的
while(n--)
cin>>s.emplace();
假设,我需要获取 n 个输入并将容器名称设置为 's'
while(n--)
int x;
cin>>x;
s.emplace(x);
这很好,但我需要减少这一步。
【问题讨论】:
“我需要删掉这一步。”为什么?替代方案几乎肯定会被编译为有效完全相同的可执行文件。 代码行数是优化速度的糟糕指标。当你做任何不平凡的事情时,输入代码就像 10%(绝对最多)的编程时间。可读的代码将使您的速度提高几个数量级。 @Frank 更像 1% @Caleth 是的,我在这里过于保守/慷慨。 @Frank 这是 90% 的会议 :-) 【参考方案1】:从 C++20 开始,您可以使用 std::ranges::copy
、std::counted_iterator
、std::istream_iterator
、std::default_sentinel
和 std::inserter
来执行此操作。 counted_iterator
+ default_sentinel
使它从流中复制 n
元素。
例子:
#include <algorithm> // ranges::copy
#include <iostream>
#include <iterator> // counted_iterator, default_sentinel, istream_iterator, inserter
#include <set>
#include <sstream> // istringstream - only used for the example
int main()
// just an example istream:
std::istringstream cin("1 1 2 2 3 3 4 4 5 5");
int n = 5;
std::set<int> s;
std::ranges::copy(
std::counted_iterator(std::istream_iterator<int>(cin), n),
std::default_sentinel,
std::inserter(s, s.end())
);
for(auto v : s) std::cout << v << ' ';
输出将只包含 3 个元素,因为流中的前 5 个元素只有 3 个唯一元素:
1 2 3
在 C++20 之前,您可以以类似的方式使用 copy_n
:
std::copy_n(std::istream_iterator<int>(cin), n, std::inserter(s, s.begin()));
警告:如果流中的元素实际上少于 n
,则两个版本都会有 undefined behavior。众所周知,流在交付您想要的内容时是不可预测的,copy_n
使错误检查变得非常困难。
为了安全起见,您可以创建一个counting_istream_iterator
以使用std::copy
从流中复制最多n
个元素,如下所示:
std::copy(counting_istream_iterator<foo>(cin, n),
counting_istream_iterator<foo>,
std::inserter(s, s.end()));
这样的迭代器可以基于std::istream_iterator
,看起来像这样:
template<class T,
class CharT = char,
class Traits = std::char_traits<CharT>,
class Distance = std::ptrdiff_t>
struct counting_istream_iterator
using difference_type = Distance;
using value_type = T;
using pointer = const T*;
using reference = const T&;
using iterator_category = std::input_iterator_tag;
using char_type = CharT;
using traits_type = Traits;
using istream_type = std::basic_istream<CharT, Traits>;
counting_istream_iterator() : // end iterator
isp(nullptr), count(0)
counting_istream_iterator(std::basic_istream<CharT, Traits>& is, size_t n) :
isp(&is), count(n + 1)
++*this; // read first value from stream
counting_istream_iterator(const counting_istream_iterator&) = default;
~counting_istream_iterator() = default;
reference operator*() const return value;
pointer operator->() const return &value;
counting_istream_iterator& operator++()
if(count > 1 && *isp >> value) --count;
else count = 0; // we read the number we should, or extraction failed
return *this;
counting_istream_iterator operator++(int)
counting_istream_iterator cpy(*this);
++*this;
return cpy;
bool operator==(const counting_istream_iterator& rhs) const
return count == rhs.count;
bool operator!=(const counting_istream_iterator& rhs) const
return !(*this == rhs);
private:
std::basic_istream<CharT, Traits>* isp;
size_t count;
T value;
;
【讨论】:
这是问题的答案,但值得注意的是它有缺点。它使处理输入错误变得更加困难。如果cin
过早到达文件结束,您将获得未定义的行为。也无法处理错误输入,例如无法转换为int
的输入。这个解决方案简短而富有表现力,但否认任何实际的错误处理。我认为处理错误的唯一方法是为cin
启用异常:See this. 但这有其自身的问题。
要明确,我之前的评论不是对答案的批评。相反,我试图警告问题的目标可能不值得付出代价。
@FrançoisAndrieux 我很高兴你提到它。我的印象是,即使在早期的 EOF 出现时,这个组合也是安全的——但是,不,当我想到它时不是这样。确保安全需要额外的开销。
如果使用范围 std::istream_iterator<int>(cin)
到 std::istream_iterator<int>()
是安全的,因为迭代器在到达 EOF 时将成为结束迭代器。这里只是不安全的,因为std::copy_n
至少假定n
元素并且从不检查end
迭代器。当您将 EOF 问题与读取n
元素的要求结合起来时,我认为没有一个标准算法可以解决这两个问题,我也会使用std::copy_n
。它需要是一个自定义算法,可以同时接受范围和最大元素数。
@FrançoisAndrieux 我重写了这个答案。非常感谢您引起我的注意。又一个幻觉消失了。 :-)【参考方案2】:
您可以做的是创建自己的可重用函数,该函数提供您想要的简洁语法。你也可以把循环放在那里。
这样,您甚至可以进行正确的错误处理,同时保持主代码简洁明了。
一个完全通用的可能如下所示:
#include <iostream>
#include <set>
#include <stdexcept>
template<typename ContainerT>
void populate(ContainerT& container, std::istream& stream, std::size_t n)
using T = typename ContainerT::value_type;
while(n--)
T tmp;
if(stream >> tmp)
container.insert(container.end(), std::move(tmp));
else
throw std::runtime_error("bad input");
int main()
std::size_t n = 5;
std::set<int> s;
populate(s, std::cin, n);
你甚至可以变得更漂亮一点。例如,支持reserve()
的容器可以在开始填充容器之前调用它:
template<typename T>
concept HasReserve = requires(T x)
x.reserve(std::size_t);
;
// ...
ContainerT result;
if constexpr(HasReserve<ContainerT>)
container.reserve(container.size() + n);
//...
【讨论】:
以上是关于如何直接从输入流中插入值?的主要内容,如果未能解决你的问题,请参考以下文章