C 特性 迭代器 上 及一些研究過程中的副產品

2022-10-06 15:54:22 字數 4298 閱讀 7978

提到迭代器我們不能不想到迭代器模式,那我就以迭代器模式作為開場白.

在我們的應用程式中常常有這樣一些資料結構:

它們是乙個資料的集合,如果你知道它們內部的實現結構就可以去訪問它們,它們各自的內部儲存結構互不相同,各種集合有各自的應用場合.說到這裡大家可能想出一大堆這樣的集合了:list,hashtable,arraylist等等。這些集合各自都有各自的個性,這就是它們存在的理由。但如果你想遍歷它你必須知道它內部的儲存細節,作為乙個集合元素,把內部細節暴露出來肯定就不好了,這樣客戶程式就不夠穩定了,在你更換集合物件的時候,比如list不能滿足需求的時候,你換成hashtable,因為以前的客戶程式過多的關注了list內部實現的細節,所以不能很好的移植。而迭代器模式就是為解決這個問題而生的:

提供一種一致的方式訪問集合物件中的元素,而無需暴露集合物件的內部表示。

比如現在有這樣乙個需求,遍歷集合內的元素,然後輸出,但是並不限定集合是什麼型別的集合,也就是未來集合可能發生改變。

思考:集合會發生改變,這是變化點,集合改變了,遍歷方法也改變,我們要保證遍歷的方法穩定,那麼就要遮蔽掉細節。找到了變化點那我們就將其隔離起來(一般使用inte***ce作為隔離手段):假設所有的集合都繼承自icollection介面,這個介面用來隔離具體集合的,將集合遮蔽在介面後面,作為遍歷我們肯定需要這樣一些方法:movenext,current,既然icollection負責資料儲存,職責又要單一,那麼就新建立乙個介面叫做iterator吧,每種具體的集合都有自己相對應的iterator實現:

下面是乙個簡易的實現**:

///

/// 集合的介面

///

public inte***ce icollection

///

/// 獲取迭代器

///

/// 迭代器

iterator getiterator();

} ///

/// 迭代器介面

///

public inte***ce iterator

}public class list : icollection

public object this[int i]

set

}#region icollection members

public int count

}public iterator getiterator()

#endregion

}程式設計客棧

public class listiteratorfrxttd : iterator

#region iterator members

public bool movenext()

}public object current

}#endregion

} ///

/// 測試

///

public class program}}

看看最後的測試,是不是不管具體的集合如何改變,遍歷**都非常穩定?而且擴充套件新的集合類也非常方便,只是新增**不會修改原來的**,符合開閉原則。當然,這麼好的解決方案微軟當然不會放過,現在c# 2.0裡已經內建了對迭代器的支援,看看system.collections, system.collections.generic命名空間,所有的集合都實現了這個介面:ienumerable,這個介面還有泛型的版本。注意到這個介面只有乙個方法:ienumerator getenumerator();,ienumerator就是迭代器的介面,相當於我的例項裡面的iterator,它也有泛型的版本。

那麼現在在.net裡所有的集合類都可以這樣訪問了:

複製** **如下:

ienumerator ienumerator = list.getenumerator();

while(ienumerator.movenext())

但是這樣訪問也太麻煩了,所以c#裡出現了foreach關鍵字,我們來看看foreach背後發生了什麼?假如有如下的**:

public static void main()

}下面是它對應的il**:

.method private hidebysig static void main() cil managed

從.locals init 那裡可以看出編譯器為我們新增了兩個區域性變數,乙個就是迭代器。

複製** **如下:

l_002d: ldloc.0

l_002e: callvirt instance class [mscorlib]system.collections.ienumerator [mscorlib]system.collections.arraylist::getenumerator()

l_0033: stloc.2

這三行**告訴我們,呼叫list的getenumerator()方法,獲取迭代器例項將其賦值給編譯器為我們新增的那個迭代器區域性變數,接著是l_0034: br.s l_0048,

br.s這個指令是強制跳轉,我們接著看

複製** **如下:

l_0048: ldloc.2

l_0049: callvirt instance bool [mscorlib]system.collections.ienumerator::movenext()

呼叫迭代器的movenext()方法,l_004e: brtrue.s l_0036 如果是true的話跳轉,

複製** **如下:

l_0036: ldloc.2

l_0037: callvirt instance object [mscorlib]system.collections.ienumerator::get_current()

l_003c: stloc.1

l_003d: ldloc.1

l_003e: callvirt instance string [mscorlib]system.object::tostring()

l_0043: call void [mscorlib]system.console::writeline(string)

獲取當前值,然後輸出

看到沒有,實際foreach後面幹的事就是獲取迭代器,然後乙個while迴圈,不過這樣一些確實簡潔多了。

說到這裡是不是

複製** **如下:

ienumerator ienumerator = list.getenumerator();

while (ienumerator.movenextwww.cppcns.com())

和複製** **如下:

foreach (object item in list)

這兩樣**是一樣的呢?如果不一樣那推薦使用哪乙個呢?當然是使用第二種,簡潔嘛,除了簡潔之外就沒有其它的了?細心讀者會發現上面的il**,

在結束迴圈後還有一大塊,可我們的c#**中並沒有啊,接著分析:

.try l_0034 to l_0052 finally handler l_0052 to l_0063

這裡說明從l_0034到l_0052是被放在try裡面的,恰好這段**是迴圈體裡的東西,l_0052到l_0063裡是放在finally裡的,看來foreach還給我們加了一

個try{}finally{}結構啊。那看看l_0052到l_0063裡是什麼東西吧:

複製** **如下:

l_0052: ldloc.2

l_0053: isinst [mscorlib]system.idisposable

l_0058: stloc.3

l_0059: ldloc.3

l_005a: b***lse.s l_0062

l_005c: ldloc.3

l_005d: callvirt instance void [mscorlib]system.idisposable::dispose()

l_0062: endfinally

l_0063: call string [mscorlib]system.console::readline()

判斷迭代器物件是否是乙個idisposable例項,如果是,那就要呼叫它的dispose()方法了(為啥它要實現idisposable介面?那肯定這個迭代器裡使用了一些非託管資源)。

看到了吧,foreach也真夠智慧型的,看來使用foreach的方式是比自己用while方式安全穩定多了。

(ps:好像是扯遠了點,不過大家一起了解一下,呵呵,其實我當初也沒想說這個,不過後來看il**有點不對勁,就當作個副產品吧)

c# 2.0裡還出現個關鍵字yield,我看了半天msdn也沒明白它的意思:

在迭代器塊中用於向列舉數物件提供值或發出迭代結束訊號。到現在還是沒明白,不過yield這個東西後面卻包含了很多東西,有一些非常「奇怪」的特性,

儲存過程中的一些片斷

create or replace procedure 儲存過程名 astype ref cursor is ref cursor 定義游標 mycursor ref cursor 定義需要的變數 變數1 number begin 變數ny to char sysdate,yyyymm strsql...

Linux移植過程中的一些錯誤

問題點 1 yaffs2根檔案系統無法掛載 failed to execute linuxrc.attempting defaults.kernel panic not syncing no init found.try passing init option to kernel.原因 mkyaff...

修改page alloc c過程中的一些筆記

1 define phys to page phys pfn to page phys page shift 2 define page to phys page page to pfn page page shift 這兩個巨集的功能分別是將struct page 和實體地址之間進行轉換 例如pa...