【发布时间】:2009-06-10 21:42:58
【问题描述】:
这在 c 中有什么等价物吗?
【问题讨论】:
-
为什么需要yield?如果你有一个数组,你可以遍历它。 Yield 与 IEnumerables 相关,您通常会在“foreach”情况下使用它。既然你不能'foreach',为什么你需要创建一个'yield'?
这在 c 中有什么等价物吗?
【问题讨论】:
Yield 由编译器实现为实现状态机的自定义类。虽然您无法轻松获得语法(除非您使用之前指定的 Fiber 方法),但您可以非常简单地自己复制结果,尽管这很乏味。下面是方法(我将在 C# 中展示,您必须根据您使用的类型在 C++ 中做适当的事情):
假设如下代码:
public IEnumerable<T> GetOddStrings(
IEnumerable<IEnumerable<string>> stringCollections)
{
foreach(var stringCollection in stringCollections)
foreach(var str in stringCollection)
{
if(str.Length %2 != 0) yield return str;
if(str.Length == 42) yield break; // 42 is BAD! Stop immediately
}
}
1) 将所有 foreach 方法展开为显式枚举器调用:
public IEnumerable<T> GetOddStrings(
IEnumerable<IEnumerable<string>> stringCollections)
{
var firstEnumerator = stringCollection.GetEnumerator();
while(firstEnumerator.MoveNext())
{
var secondEnumerator = firstEnumerator.Current.GetEnumerator();
while(secondEnumerator.MoveNext())
{
var str= secondEnumerator.Current;
if(str.Length %2 != 0) yield return str;
if(str.Length == 42) yield break;
}
}
}
2) 将所有局部变量移动到方法的顶部:
public IEnumerable<T> GetOddStrings(
IEnumerable<IEnumerable<string>> stringCollections)
{
IEnumerator<IEnumerable<string>> firstEnumerator;
IEnumerator<string> secondEnumerator;
string str;
firstEnumerator = stringCollections.GetEnumerator();
while(firstEnumerator.MoveNext())
{
secondEnumerator = firstEnumerator.Current.GetEnumerator();
while(secondEnumerator.MoveNext())
{
str= secondEnumerator.Current;
if(str.Length %2 != 0) yield return str;
if(str.Length == 42) yield break;
}
}
}
3) 移动到带有嵌套 switch 语句的循环结构。
a) 更改状态并为每个收益返回继续循环。
b) 反转 if 条件
c) 每个退出条件的屈服中断(下面我们反转 if)。
public IEnumerable<T> GetOddStrings(
IEnumerable<IEnumerable<string>> stringCollections)
{
IEnumerator<IEnumerable<string>> firstEnumerator;
IEnumerator<string> secondEnumerator;
string str;
int state = 0;
while(true)
{
switch(state)
{
case 0:
firstEnumerator = stringCollections.GetEnumerator();
// This could be "yield break" but I want to show how you
// could split ifs with significant code in the else
if(!firstEnumerator.MoveNext())
{
state = 1;
continue;
}
secondEnumerator = firstEnumerator.Current;
if(!secondEnumerator.MoveNext) continue;
state = 2;
if(str.Length %2 != 0) yield return str;
continue;
case 1:
yield break;
case 2:
if(str.Length == 42) yield break;
state = 0;
continue;
}
}
}
4) 进入一个类并从您的方法中返回该类: a)yield break 变成“return false”; b) 收益回报变成“this.Current = ??; return true;”
public IEnumerable<T> GetOddStrings(
IEnumerable<IEnumerable<string>> stringCollections)
{
return new OddStringEnumerable(stringCollections);
}
private class OddStringEnumerable : IEnumerable<string>
{
IEnumerable<IEnumerable<string>> stringCollections;
IEnumerator<IEnumerable<string>> firstEnumerator;
IEnumerator<string> secondEnumerator;
string str;
int state;
public OddStringEnumerable(IEnumerable<IEnumerable<string>> stringCollections)
{
this.stringCollections = stringCollections;
}
public string Current { get; private set; }
public bool MoveNext()
{
while(true)
{
switch(state)
{
case 0:
firstEnumerator = this.stringCollections.GetEnumerator();
if(!this.firstEnumerator.MoveNext())
{
this.state = 1;
continue;
}
this.secondEnumerator = this.firstEnumerator.Current;
if(!secondEnumerator.MoveNext) continue;
this.state = 2;
if(str.Length %2 != 0)
{
this.Current = str;
return true;
}
continue;
case 1:
return false;
case 2:
if(str.Length == 42) return false;
this.state = 0;
continue;
}
}
}
}
5) 酌情优化。
【讨论】:
纤维?哦,这个:
使用 Fibers 的原生 C++ 的收益返回迭代器
http://www.codeproject.com/KB/library/fiber-based_iterator.aspx
【讨论】:
虽然 C 在枚举集合方面没有与 yield 相同的概念,但它确实具有创建协程和纤维的能力。
这里有一些可能感兴趣的维基百科文章:
http://en.wikipedia.org/wiki/Setcontext http://en.wikipedia.org/wiki/Setjmp/longjmp
【讨论】:
Coroutines in C 使用了一些预处理程序,但实现了一个相当自然的(相对于 C 中的任何其他内容)yield。
而你会用 Python 写这个:
"""This is actually a built-in function.
def range(start, stop, step):
i = start
while i < stop:
yield i
i = i + step
"""
if __name__ == '__main__':
import sys
start = int(sys.argv[1]) if len(sys.argv) > 2 else 0
stop = int(sys.argv[2]) if len(sys.argv) > 2 else int(sys.argv[1])
step = int(sys.argv[3]) if len(sys.argv) > 3 else 1
for i in range(start, stop, step):
print i,
print
coroutine.h 让您可以用 C 语言编写:
#include <coroutine.h>
#include <stdio.h>
int range(int start, int stop, int step) {
static int i;
scrBegin;
for (i = start; i < stop; i += step)
scrReturn(i);
scrFinish(start - 1);
}
int main(int argc, char **argv) {
int start, stop, step, i;
start = argc > 2 ? atoi(argv[1]) : 0;
stop = argc > 2 ? atoi(argv[2]) : atoi(argv[1]);
step = argc > 3 ? atoi(argv[3]) : 1;
while ((i = range(start, stop, step)) >= start)
printf("%d ", i);
printf("\n");
}
$ cc 范围.c
$ ./a.out 10
0 1 2 3 4 5 6 7 8 9
对于更复杂且需要重入的东西,Python 中的Hamming numbers:
def hamming():
yield 1
i2 = (2*x for x in hamming())
i3 = (3*x for x in hamming())
i5 = (5*x for x in hamming())
m2, m3, m5 = i2.next(), i3.next(), i5.next()
while True:
if m2 < m3:
if m2 < m5:
yield m2
m2 = i2.next()
else:
if m2 > m5: yield m5
m5 = i5.next()
elif m2 == m3: m3 = i3.next()
elif m3 < m5:
yield m3
m3 = i3.next()
else:
if m3 > m5: yield m5
m5 = i5.next()
if __name__ == '__main__':
import sys
it = hamming()
for i in range(str(sys.argv[1]) if len(sys.argv) > 1 else 25):
print it.next(),
print
和C:
#include <coroutine.h>
#include <stdio.h>
int hamming(ccrContParam) {
ccrBeginContext;
ccrContext z[3];
int m2, m3, m5;
ccrEndContext(state);
ccrBegin(state);
state->z[0] = state->z[1] = state->z[2] = 0;
ccrReturn(1);
#define m2_next (2*hamming(&state->z[0]))
#define m3_next (3*hamming(&state->z[1]))
#define m5_next (5*hamming(&state->z[2]))
state->m2 = m2_next, state->m3 = m3_next, state->m5 = m5_next;
while (1) {
if (state->m2 < state->m3) {
if (state->m2 < state->m5) {
ccrReturn(state->m2);
state->m2 = m2_next;
} else {
if (state->m2 > state->m5) ccrReturn(state->m5);
state->m5 = m5_next;
}
} else if (state->m2 == state->m3) state->m3 = m3_next;
else if (state->m3 < state->m5) {
ccrReturn(state->m3);
state->m3 = m3_next;
} else {
if (state->m3 > state->m5) ccrReturn(state->m5);
state->m5 = m5_next;
}
}
ccrFinish(-1);
}
int main(int argc, char **argv) {
int count = argc > 1 ? atoi(argv[1]) : 25, i;
ccrContext z = 0;
for (i = 0; i < count; i++)
printf("%d ", hamming(&z));
printf("\n");
}
$ cc hamming.c
$ ./a.out
1 2 3 4 5 6 8 9 10 12 15 16 18 20 24 25 27 30 32 36 40 45 48 50 54
【讨论】:
没有。不过,在 Windows 上,您可以使用纤维来实现类似的效果。
【讨论】:
没有。但是,您可以使用setjmp, longjmp 在 C 中实现类似的效果,但这非常很棘手。
【讨论】:
在 C# 中,yield 简化了为集合创建 IEnumberables。
在 C++ 中,您必须使用 STL 迭代器。
【讨论】: