基于C ++范围的循环,具有特殊情况的第一项?

时间:2019-02-22 17:06:18

标签: c++

我经常发现自己的代码如下:

bool isFirst = true;
for(const auto &item: items)
{
    if(!isFirst) 
    { 
       // do something
    }
    // Normal processing
    isFirst = false;
}

似乎应该有一种更好的表达方式,因为它是函数的一种常见模式,就像“ join”一样。

8 个答案:

答案 0 :(得分:7)

除非您在arrayvector之类的容器上循环,您可以在其中获取对象的地址并进行比较,否则您无法知道在for循环范围内要访问哪个元素它到第一个项目的地址,以弄清楚您在容器中的位置。如果容器提供按值查找,也可以执行此操作,可以查看find操作返回的迭代器是否与begin迭代器相同。

如果您需要对第一个元素进行特殊处理,则可以使用传统的for循环

for (auto it = std::begin(items), first = it, end = std::end(items); it != end; ++it)
{
    if (it == first)
    {
        // do something
    }
    // Normal processing
}

如果可以将需要执行的操作从循环中排除,则可以使用基于范围的for循环,然后将处理放在循环之前,如

// do something
for(const auto &item: items)
{
    // Normal processing
}

答案 1 :(得分:5)

也许您正在寻找for_first_then_each?它以迭代器的形式获取您的范围,并将第一个函数应用于第一个元素,第二个函数应用于其余元素。

#include <iostream>
#include <vector>

template<typename BeginIt, typename EndIt, typename FirstFun, typename OthersFun>
void for_first_then_each(BeginIt begin, EndIt end, FirstFun firstFun, OthersFun othersFun) {
    if(begin == end) return;
    firstFun(*begin);
    for(auto it = std::next(begin); it != end; ++it) {
        othersFun(*it);
    };
} 

int main() {

    std::vector<int> v = {0, 1, 2, 3};

    for_first_then_each(v.begin(), v.end(),
        [](auto first) { std::cout << first + 42 << '\n'; },
        [](auto other) { std::cout << other - 42 << '\n'; }
    );

    // Outputs 42, -41, -40, -39

    return 0;
}

答案 2 :(得分:2)

一个有趣的替代解决方案是使用自定义迭代器,如果我不小心就不会在生产中使用它。

int main() {
  std::vector<int> v{1,2,3,4};

  for (const auto & [is_first,b] : wrap(v)) {
    if (is_first) {
      std::cout << "First: ";
    }
    std::cout << b << std::endl;
  }
}

玩具实现可能如下所示:

template<typename T>
struct collection_wrap {
  collection_wrap(T &c): c_(c) {}

  struct magic_iterator {
    bool is_first = false;
    typename T::iterator itr;

    auto operator*() {
      return std::make_tuple(is_first, *itr);
    }

    magic_iterator operator++() {
      magic_iterator self = *this;
      itr++;
      //only works for forward
      is_first = false;
      return self;
    }

    bool operator!=(const magic_iterator &o) {
      return itr != o.itr;
    }
  };

  magic_iterator begin() {
    magic_iterator itr;
    itr.is_first = true;
    itr.itr = c_.begin();

    return itr;
  }

  magic_iterator end() {
    magic_iterator itr;
    itr.is_first = false;
    itr.itr = c_.end();

    return itr;
  }


  T &c_;
};

template<typename Collection>
collection_wrap<Collection>
wrap(Collection &vec) {
  return collection_wrap(vec);
}

答案 3 :(得分:1)

检查对象地址以查看是否为第一项:

for(const auto &item: items)
{
    if (&item != &(*items.begin())
    { 
       // do something for all but the first
    }
    // Normal processing
}

答案 4 :(得分:0)

随着Ranges进入C ++ 20,您可以将其分为两个循环:

for (auto const& item : items | view::take(1)) {
    // first element only (or never executed if items is empty)
}

for (auto const& item : items | view::drop(1)) {
    // all after the first (or never executed if items has 1 item or fewer)
}

如果您不想等待C ++ 20,请查看支持这两个操作的range-v3

这对于输入范围(例如items实际上是从cin读取的范围)将无法正常工作,但对于任何正向或更好的范围都可以正常工作(我我猜items是这里的容器,所以应该没问题。


实际上,更简单的版本是使用enumerate(仅存在于range-v3中,而不存在于C ++ 20中):

for (auto const& [idx, item] : view::enumerate(items)) {
    if (idx == 0) {
         // first element only
    }
    // all elements
}

答案 5 :(得分:0)

C++20 开始,您可以使用 init 语句稍微改进您的 range-based for loop。 init 语句允许您将 isFirst 标志移动到循环范围内,以便该标志在循环外不再可见:

std::vector<int> items { 1, 2, 3 };

for(bool isFirst(true); const auto &item: items) {
    if(!isFirst) {
        std::cout << "Do something with: " << item << std::endl;
    }

    std::cout << "Normal processing: " << item << std::endl;
    isFirst = false;
}

输出:

<块引用>

正常处理:1
做点什么:2
正常处理:2
做点什么:3
正常处理:3

Code on Wandbox

答案 6 :(得分:-1)

在C ++中仍然有效的一种方法是使用宏:

#include <iostream>
#include <vector>

#define FOR(index, element, collection, body) { \
    auto &&col = collection; \
    typeof(col.size()) index = 0; \
    for(auto it=col.begin(); it!=col.end(); index++, it++) { \
        const auto &element = *it; \
        body; \
    } \
}

using namespace std;

int main() {
    vector<int> a{0, 1, 2, 3};
    FOR(i, e, a, {
        if(i) cout << ", ";
        cout << e;
    })
    cout << endl;

    FOR(i, e, vector<int>({0, 1, 2, 3}), {
        if(i) cout << ", ";
        cout << e;
    })
    cout << endl;

    return 0;
}

打印:

0, 1, 2, 3
0, 1, 2, 3

与其他选项相比,该解决方案是简洁的。不利的一面是,index在循环的每次迭代中都要进行测试和递增-通过增加宏的复杂度并使用bool first代替index可以避免这种情况,但是要使用宏中的indexbool first涵盖了更多的用例。

答案 7 :(得分:-2)

我假设您想知道如何检索第一个元素,可以使用arrayvector来实现。

我将在这里显示array

首先将其包含在您的代码中:

#include <array>

然后相应地转换数组:

    std::array<std::string, 4> items={"test1", "test2", "test3", "test4"};

    for(const auto &item: items)
    {
        if(item == items.front()){
           // do something     
           printf("\nFirst: %s\n", item.c_str()); //or simply printf("\nFirst:"); since you gonna output a double element
        }
        // Normal processing
           printf("Item: %s\n", item.c_str());
    }
    return 0;
}