使用for_each迭代以NULL结尾的字符串数组

5

使用for_each迭代一个以NULL结尾的字符串是可行的:

const char *name = "Bob";

void func(const char &arg)
{
   cout << arg;
}

int main()
{
    for_each(name, name + strlen(name), func);
}

对于以NULL结尾的字符串列表(无需先确定列表的总长度),是否有类似的方法可行?例如:

const char *names[] = { "Bob", "Adam", "Simon", NULL };

3
在 C++ 中,字符串列表应使用 std::list<std::string>(如果更适合则使用 std::vector)表示。这将一次性解决您所有的问题。 - Björn Pollex
3
以何种方式相似?在您的示例中,您正在确定字符串的长度。 - eq-
@eq- 我意识到在我的示例中我正在确定字符串的长度,但我宁愿不这样做。而且当有一个单独的字符串时,确定长度是微不足道的,但是对于一个大的字符串数组来说,这变得更加繁琐。我真的希望有一些东西可以作为结束迭代器传递进去,这将自动使for_each检测到数组中的结尾NULL作为列表的结尾。 - user3317
10个回答

9

std::for_each可以在范围内"迭代",所以如果要将其与长度不确定的数组一起使用,则需要使用自定义迭代器来指示数组的结尾(即NULL成员)。如果您坚持使用以NULL结尾的char*数组,当然可以为其创建自己的for_each函数,例如如下:

template <typename Function>
void for_each_in_null_terminated_cstring_array(const char** array, Function f)
{
    while (*array) {
        f(*array);
        array++;
    }
}

const char *names[] = { "Bob", "Adam", "Simon", NULL };
for_each_in_null_terminated_cstring_array(names, func);

我并不是真正推荐这种解决方案。

编辑:是的,更通用总是更好的,不是吗?

template <typename T, typename Function>
void for_each_in_null_terminated_array(T* array, Function f)
{
    while (*array) {
        f(*array);
        array++;
    }
}

(这是我之前提到的以空字符(“false”)结尾的迭代器的实现 - 根据下面的建议进行了一些更改。它应该是一个真正的InputIterator)
template <class T>
class nt_iterator: public std::iterator<std::input_iterator_tag, T>
{
public:
    typedef typename nt_iterator<T>::pointer pointer;
    typedef typename nt_iterator<T>::value_type value_type;

    nt_iterator(): p(), pte(true) {}
    nt_iterator(pointer p_): p(p_), pte(!p_) {}
    nt_iterator(const nt_iterator<T>& rhs): p(rhs.p), pte(rhs.pte) {}
    nt_iterator<T>& operator++() {
        ++p;
        if (!*p) pte = true; // once past-the-end, always past-the-end
        return *this;
    }
    nt_iterator<T> operator++(int) {
        nt_iterator n(*this);
        operator++();
        return n;
    }
    bool operator==(const nt_iterator<T>& rhs) {
        return pte && rhs.pte || p == rhs.p;
    }
    bool operator!=(const nt_iterator<T>& rhs) {
        return !(operator==(rhs));
    }
    value_type operator*() { return *p; }

private:
    pointer p;
    bool pte; // past-the-end flag
};

以及它的使用方式:

void print(const char* str);

int main()
{
    const char* array[] = {"One", "Two", "Three", NULL, "Will you see this?"};
    std::for_each(nt_iterator<const char*>(array),
                  nt_iterator<const char*>(),
                  print);
}

使用std::for_each可能会比循环版本慢一点,因为需要增加等价性检查的数量。当然,与例如打印文本相比,速度差异微不足道,但应注意std::for_each并不能神奇地使循环更快(实际上,如果您期望过高,您可能会惊讶地看到编译器供应商如何定义该函数)。


3
不,你不需要“按定义”确定长度。这可以通过自定义迭代器实现。虽然这样做有些丑陋,但是它是可行的。 - Fred Foo
仍然,我推荐这个解决方案,给它加上一个赞。 - Fred Foo
这非常方便!小小的批评:我会说你不应该使用== NULL,而应该依赖于operator!== T(),例如,pte = !*p;。此外,指定通用的过去结束迭代器的通常方法是使用默认构造函数,例如std::istream_iterator - Jon Purdy
我同意 - 我在一些地方混淆了空指针和否定。实际上,我最初使用了 pte = !*p,但我考虑到所有指针,并且认为它似乎太棘手了 - 尽管这是我在自己的代码中使用的版本 ;) - eq-
我觉得很棒。如果我碰到这种情况,我肯定会使用类似的东西。 - Jon Purdy
你在 pte 的初始化中丢失了一个 * 吗? 应该是 pte(!*p_),对吧? 在这个例子中,T 是一个 const char *,所以 pointer 应该是 const char * *,对吗? - Adrian McCarthy

3

带有

const char *names[] = { "Bob", "Adam", "Simon" };

你可以直接调用。
std::for_each(names, names + sizeof(names)/sizeof(names[0]), func );

或者,更好的方法是使用两个辅助函数:链接
std::for_each(begin(names), end(names), func );

当然,一旦数组分解为指针,此方法就会失效(但至少编译器不会接受它)。如果你必须依赖于该结尾处的空值,那么你需要编写自己的循环函数或者预先计算数量,例如使用std::strlen()
std::ptr_diff_t num = std::find( names
                               , names + std::numeric_limits<std::size_t>::max()
                               , NULL);
std::for_Each( names, names+num, func );

使用以NULL结尾的字符串数组时,如果从末尾指针中减去一个元素"names + sizeof(names)/sizeof(names[0]) - 1",则可以使用这种技术。 - user3317
@Andrew:要么这样,要么让func()能够处理NULL参数。 - sbi
1
我指的是OP的要求“无需事先确定列表的总长度”。您正在使用sizeof来完成这个任务。 - Fred Foo
@Andrew:那么你应该选择eq的答案。它很好,我已经点赞了。 - sbi
@larsmans:啊,我明白了。我把它读成“我不想要两次迭代所有项目”。是我的错。 - sbi
显示剩余2条评论

3

在Basilevs的答案基础上,进一步提供一个完整可用的解决方案。

可以定义一个自定义迭代器如下所示:

template <class T>
class NullTerminatedIterator
    :public std::iterator<std::forward_iterator_tag,
    T,ptrdiff_t,const T*,const T&>
{
public:
    typedef NullTerminatedIterator<T> NTI;

    NullTerminatedIterator(T * start): current(start) {}
    NTI & operator++() {current++; return *this;}
    T & operator*() { return *current; } 
    static NTI end() { return NTI(0); }
    bool operator==(const NTI & that) { return *current == *that.current; }
    bool operator!=(const NTI & that) { return *current != *that.current; }
private:
    T * current;
};

然后像这样使用:

const char *names[] = {"Bob", "Adam", "Simon", NULL};

NullTerminatedIterator<char*> iter((char**)names);

for_each(iter, NullTerminatedIterator<char*>::end(), func);

NullTerminatedIterator的基类取自于这个定制迭代器问题。

这只在for_each调用期间遍历列表,正如所请求的那样。


@Basilevs - 你是指静态函数还是静态常量变量nullNTI?这段代码在VS2010中运行正常。 - user3317
@Basilevs - 也许我只需要将0转换为(T*),但是在使用NTI(0)进行初始化时,它无法在VS2010中编译。 - user3317
@Basilevs - 原来NTI(0)不起作用只是由于其他某些东西在当时没有编译的结果 - 现在已经在答案中修复,同时在for_each行中正确使用了NullTerminatedIterator。再次感谢! - user3317
天啊,我刚刚才注意到你是一个 OP :) - Basilevs

1

已经有多个答案告诉你可以做什么。然而,对于你的特定问题,答案只是“不行,你不能” :)


@sbi:嗯...那仍然会迭代两次序列,我认为OP想要的是范围只被迭代一次。但是使用for_each。我相信这是不可能的。 - Armen Tsirunyan
可以的。请看我对eq-'s回答的回复。 - Fred Foo
1
是的,这是可能的。你只需要编写一个迭代器类型,在其中结束迭代器是“特殊的”,它不指向特定位置,而是仅与指向空值的任何迭代器相等。这有点类似于标准库中已经存在的流迭代器。 - jalf
@larsmans,@jalf:是的,我想是这样。但这不值得,对吧?无论如何,我的回答是错误的,我很快就会删除它。 - Armen Tsirunyan
1
@Armen:我从未说过这是值得的,只是说它是可能的;)(这也是支持Alexandrescu的“迭代器必须消失”观点中更有力的论据之一。像这样的东西作为一个范围表达会更自然。) - jalf
显示剩余4条评论

0

你可以使用 sizeof() 函数来处理编译时确定大小的数组。

const char *names[] = { "Bob", "Adam", "Simon" };
std::for_each(names, names + sizeof(names)/sizeof(*names), [](const char* arg) {
    std::cout << arg << "\n";
});
std::cin.get();

对于动态大小的数组,您应该使用std::vector<std::string>并对其进行迭代。

请原谅我使用lambda表达式,您的编译器(可能)不支持它们。


0

相反,将它们添加到容器中,并使用 for_each 进行迭代。
我在示例中使用了 vector:

void function(string name)
{
    cout << name;
}

int main()
{
    vector<string> nameVector;

    nameVector.push_back("Bob");
    nameVector.push_back("Adam");
    nameVector.push_back("Simon");

    for_each(nameVector.begin(), nameVector.end(), function);

    return 0;
}

0

你能否将传递给函数的参数替换为对指向const char的指针的引用,以实现你想要的效果。就像这样:

const char *names[] = { "Bob", "Adam", "Simon" };

void func( const char* &arg )
{
   cout << arg << endl;
}

int main()
{
    for_each( names, 
              names + sizeof( names ) / sizeof( names[ 0 ] ), 
              func );
}

显然,对于以NULL结尾的字符串数组,只需从数组大小中减去1即可...


0
template <class T>
struct NullTerminatedIterator {
  typedef NullTerminatedIterator<T> NTI;
  T * current;
  NTI & operator++() {current++; return this;}
  T & operator*() {return *current;} 
  NullTerminatedIterator(T * start): current(start) {}
  static NTI end() {return  NTI(0);}
  bool operator==(const NTI & that) {return current==that.current;}

}

我认为这就是我想要的,但实际上我无法让它正常工作。如果使用这个迭代器,for_each 行会是什么样子? - user3317

0
    // C version 
    const char* vars[16]={"$USER","$HOME","$DISPLAY","$PASSWORD",0};

    for(const char** pc = vars; *pc!=0; pc++)
    {
            printf("%s",*pc);
    }

0

我知道这不是for_each,但我想使用旧的常规for循环来完成相同的操作。这个来自MSDN博客

将双null终止字符串重新解释为一个带有空字符串作为终止符的字符串列表,使得编写遍历双null终止字符串的代码非常简单:

for (LPTSTR pszz = pszzStart; *pszz; pszz += lstrlen(pszz) + 1) {
   // ... do something with pszz ...
}

对我来说看起来很干净!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接