<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      閉包解析(Fun with closure)

      我發現英文標題真的非常不給力。

      這篇隨筆是對“閉包”這個東西的簡單介紹。為了輕松一些,用了Fun with closure這個標題。

      有點兒像閉包的東西

      我先找了幾個有點兒像閉包的東西。擺出來看看。第一個東西是C++的Functor:

       1 struct add_x {
       2     add_x(int x) : m_x(x) { }
       3     int operator() (int y) { return m_x + y; }
       4  
       5 private:
       6     int m_x;
       7 };
       8 
       9 int value = 1;
      10 
      11 std::transform(input, input + size, result, add_x(value));

      這段代碼期望將 input 集合中的每一個元素使用 add_x 映射到 result 集合中。這里,add_x是一個 functor。為了將在函數棧空間上定義的變量value引入到functor中來,我們必須采用成員變量的方式對其進行復制(或者引用)。這樣一來,好像在棧上定義的值value被帶到了另外一個上下文中一樣。

      我們再來看看一段 C# 的代碼:

       1 IEnumerable<int> Transform(
       2     IEnumerable<int> input,
       3     Func<int, int, int> transformer, 
       4     int factor) {
       5     foreach (int value in input) {
       6         yield return transformer(value, factor);
       7     }
       8 }
       9  
      10 int Add(int x, int y) { return x + y; }
      11  
      12 void Main() {
      13   int[] array = { 1, 2, 3, 4, 5 };
      14   int factor = 1;
      15   Transform(array, Add, factor).Dump();
      16 }

      這段代碼同樣也是在一個集合上應用 Add 方法。為了將在 Main 函數中定義的變量 factor 引入到Add方法中,我們將factor變量作為參數傳入了Transform函數中,進而傳入了transformer委托中。

      做一個閉包

      上面兩段代碼都像是“閉包”但是他們不是。我們接下來要做一個“真的”閉包,用C#吧,雖然我很想用Javascript。

      第一件事情就是將“函數”看作 first-class data,或者稱之為first-class function。什么是 first-class function呢?請看維基(http://en.wikipedia.org/wiki/First-class_function),如果你不喜英文我簡要解釋:first-class function意味著在語言中,函數可以被用作參數傳遞到其他的函數中;函數可以當作返回值被其他函數返回;函數可以作為數據存儲在其他數據結構中。好的我們現在就把函數看作 first-class function:

      1 Func<string, string, bool> predicator = delegate(string value, string part) {
      2   return value.Contains(part);
      3 };

      當然我們還可以將其寫為 lambda 表達式:

      1 Func<string, string, bool> predicator = (value, part) => value.Contains(part);

      現在,如果我們希望知道一個字符串是否包含了 “jumps”這個字符串的時候,我們可以用如下的代碼:

      string data = "A quick brown fox jumps over a lazy dog.";
      predicator(data, "jumps")

      但是我們不太喜歡“jumps”這個參數,我們從參數表中解放他,于是我們把他挪到了外面作為一個變量,而在函數數據體中直接使用這個變量。

      1 string partVariable = "jumps";
      2 Func<string, bool> predicator = (value) => value.Contains(partVariable);
      3 string data = "A quick brown fox jumps over a lazy dog.";
      4 predicator(data).Dump();

      現在你得到了閉包!恭喜。

      什么是閉包?

      那么什么是閉包呢?這里有兩個定義。我們先來看睡覺前專用的定義:在計算機科學中(而不是數學中),一個閉包是一個函數或者一個函數的引用,以及他們所引用的環境信息(就像是一個表,這個表存儲了這個函數中引用的每一個沒有在函數內聲明的變量)。

      也就是閉包總是要有兩個部分的,一部分是一個函數,另一個部分是被這個函數“帶走”的,但是卻不是在這個函數中聲明的變量表(稱之為 free variables 或者 outer variables)。

      還有一個不是那么呆的定義:閉包允許你封裝一些行為(函數就是行為),像其他對象一樣將它傳來傳去(函數是first-class function),但是不論怎樣,它仍然保持著對原來最初上下文的訪問能力(它還能訪問到 outer variables)。

      很神奇,那么他是怎么實現的呢?

      我們以C#為例,但是其他語言的實現方式大同小異。這里可能C++的實現需要注意問題最多,我們會單獨的說明。C#代碼來也:

      1 string key = "u";
      2 var result = words.Where(word => word.Contains(key));

      這是一段非常簡單的代碼,你可以編譯,然后用反編譯器反向一下就會看到編譯器幫你做的事情,我把這些事情用以下的圖表示:

      編譯器為我們做了兩件事情:

      (1)剛才提到閉包有兩個要素,一個是函數,另一個是函數引用的外部變量。OK,這里函數就是 word => word.Contains(key),而外部變量就是 key。編譯器將這兩個東西封裝成了一個類:ClosureHelper。
      (2)將原本在函數“棧”上分配的變量 key,替換為了 closureHelper.key。此時,變量就跑到堆上去了。所以即使函數滿世界跑,他也總能夠訪問到最初的那個變量closureHelper.key。

      看到了嗎?這個變量的生存期實際上延長了!

      Closure的“詭異”現象

      在了解了實現細節之后。我們可以來探討一下使用 Closure 可能出現的“詭異”現象。說“詭異”其實只要套用 Closure 的實現細節,他們實際上也很普通。這些詭異現象的成因基本上都是一個:outer-variable在closure中被改變了。

      例子1:

      假設我們有如下的初始代碼:

      1 var words = new List<string> {
      2     "the", "quick", "brown", "fox", "jump", 
      3     "over", "a", "lazy", "dog"
      4 };
      5  
      6 string key = "u";
      7 var result = words.Where(word => word.Contains(key));

      我們比較容易知道輸出是:quick和jump。但是如果這個程序變成:

      1 string key = "u";
      2 Func<string, bool> predicate = word => word.Contains(key);
      3 key = "v";
      4 
      5 var result = words.Where(predicate);

      那么輸出又是什么呢?考慮到key實際上是closureHelper.key那么很容易知道在predicate執行的時候,key已經變成了"v",因此輸出是:over。還想不明白的打開一個LINQPad試一下就知道了:-)。

      例子2:

       1 var actionList = new List<Action>();
       2  
       3 for (int i = 0; i < 5; ++i) {
       4     actionList.Add(
       5         () => Console.WriteLine(i));
       6 }
       7  
       8 foreach (Action action in actionList) {
       9     action();
      10 }

      如果你面試,也許會碰到這個東西。他的輸出是:5 5 5 5 5。這個用語言解釋起來不太容易,請看下面的圖:

      ClosureHelper是在 for 循環體之外創建的,也就是 outer-variable 被 capture 的時候,全局只有一個實例。因此i實際上在第一個循環之后其值是5。這樣,在action真正執行的時候只可能輸出5。

      為了修正這個問題,我們不應當用 i 作為 outer variable 而是應當在循環體內定義 outer-variable:

       1 var actionList = new List<Action>();
       2  
       3 for (int i = 0; i < 5; ++i) {
       4     int outerVariable = i;
       5     actionList.Add(
       6         () => Console.WriteLine(outerVariable));
       7 }
       8  
       9 foreach (Action action in actionList) {
      10     action();
      11 
      12 }

      這樣,執行過程就變成了:

      輸出為期望值:0 1 2 3 4。

      事實上,如果是 java,根本不允許第一種寫法。屬于語法錯誤。

      例子3

      不難想到,在closure中改變outer variable同樣可以影響到其他上下文中的outer variable引用。例如:

      1 int variable = 2;
      2  
      3 Action action = delegate { variable = 3; };
      4 action();

      執行之后,variable 的值是3。

      你看到了,在closure中改變outer varaible的值還是不要做為好。實際上,不更改 closure 中 outer variable 的值有額外的好處:

      (1)避免過度用腦導致的脫發;
      (2)這類代碼更容易移植到函數式語言,例如 F# 等。因為在這些語言中 immutable 是一個基本的規則。

      關于函數式語言的一些范式已經超出了本文的范圍,我建議大家看看以下的博客:

      (1)http://diditwith.net/default.aspx
      (2)http://blogs.msdn.com/b/dsyme/

      C++ 的細節

      方才提到了,由于閉包使得被 capture 的變量的生存期實際上延長了!這種處理方式對于C#,Java,F#等托管環境下的語言來說是沒有什么問題的。但是C++(Native,對不起我真的討厭用 C++ CLI 寫程序)沒有垃圾收集器。編譯器怎么處理?難道也會延長生存期?答案是,不會。你需要自己搞定這些,否則沒準兒就會出現 Access Violation。

      那么我怎么搞定呢?答案是控制 Capture Style。也就是向編譯器說明,我如何引用 outer variable。我們先看看 C++ 中如何構造閉包吧。

      C++中的閉包聲明可以用 lambda表達式來做,其包含三個部分:

      (1)Capture Method,也就是我們關注的capture style;
      (2)Parameter List,即參數表,和普通的 C/C++ 函數一樣;
      (3)Expression Body:即函數的主體,和普通的 C/C++ 函數一樣;

      第(2)和第(3)點都不用多說。關鍵是第一點。第一點要想說清楚真的要說不少廢話,不如列表來的清晰,這個列表來源于 http://www.cprogramming.com/c++11/c++11-lambda-closures.html

      [] 什么都不捕獲
      [&] 按照引用捕獲所有的outer variables
      [=] 通過復制(按值)捕獲所有的outer variables
      [=, &foo] 通過復制捕獲所有的outer variables,但是對于 foo 這個變量,用引用捕獲
      [bar] 通過復制捕獲bar這個變量,其他的變量都不要復制;
      [this] 通過復制的方式捕獲當前上下文中的this指針;

      這種Capture方法的指定直接影響到了編譯器生成的Helper類型的成員變量的聲明形式(聲明為值還是引用)進而影響程序的邏輯。Helper類型將在Capture時生成,屆時將根據Capture的類型進行復制或者引用。舉一個例子。

      1 {
      2     outer_variable v; // [1]
      3  
      4     std::function<void(void)> lambda = [=] () { v.do_something(); }; // [2]
      5     lambda(); // [3]
      6 }

      在【1】處,outer_variable創建了一個實例,outer_variable 的默認構造函數被調用。假設我們記這個實例為 v。

      在【2】處比較繁:
      首先,一個 closure 實例被創建,并且 v 以 value 的形式進行 capture 被 closure 實例使用,因而 outer_variable 的復制構造函數被調用。我們記這個 outer_variable 的實例為 v'。
      其次,觸發 std::function::ctor(const T&),其內部會為類型T(目前這里是一個匿名的 closure 類型)進行復制構造,于是,v' 作為其中的一個按值引用的成員變量也被復制構造,因此 outer_variable 的復制構造函數被調用。我們記這個 outer_variable 的實例為 v''。

      【2】完畢之后,rvalue 的 closure 實例被析構,使得 v' 被析構。

      【3】實際上調用的是 v'' 的 do_something 方法;

      是不是很煩?當然,在按值 capture 的方式下,顯然無法更改 outer varaible 的值。

      按引用 capture 顯然不需要頻繁的復制構造 outer varaible 實例。并且,你可以在 closure 中更改 outer variable 的值以影響最初上下文中的變量。但是需要特別注意變量的生存期。

      std::function<void(void)> func;
       
      {
          outer_variable v; // [1]
          func = [&] () { v.do_something(); }; // [2]
      } // [3]
       
      func(); // undefined behavior.

      【1】outer_variable 默認構造函數調用,創建實例 v。
      【2】closure helper 實例構造,按引用 capture 到 v,由于是按引用因此沒有復制構造函數調用,closure helper 實例使用 std::function 的構造函數初始化 std::function 對象。rvalue closure 實例析構。
      【3】由于超出了作用域,v析構。此時 func 對象的 closure helper 實例 capture 到的 v 的引用已然不存在了。

      此時調用 func 會造成未定義行為。具體的參見 C++ Spec:

      5.1.2 Lambda expressions [expr.prim.lambda]

      22 - [ Note: If an entity is implicitly or explicitly captured by reference, invoking the function call operator of the corresponding lambda-expression after the lifetime of the entity has ended is likely to result in undefined behavior. —end note ]

      結尾

      好了,寫完了。希望到此你已經對 closure 有了一個了解,知道了編譯器是怎么處理他的。也知道了使用 closure 的一些坑。如果你發現本文有什么地方不妥,就狠狠的砸過來把,歡迎討論:-)。

      posted @ 2012-11-23 22:54  TW-劉夏  閱讀(6237)  評論(7)    收藏  舉報
      主站蜘蛛池模板: 久久大香伊蕉在人线免费AV| 欧美午夜成人片在线观看| www国产精品内射熟女| 东方av四虎在线观看| 亚洲天堂男人的天堂在线| 国产亚洲精品在av| 欧美乱大交aaaa片if| 九色综合狠狠综合久久| 免费无码又爽又刺激成人| 临湘市| 欧美性色黄大片www喷水| 亚洲高清WWW色好看美女| 亚洲愉拍一区二区三区| 日韩一区二区三区高清视频| AV无码不卡一区二区三区| 国产精品爽爽久久久久久竹菊| 木里| 人妻蜜臀久久av不卡| 色综合久久久久综合体桃花网| 中文乱码字幕在线中文乱码| 亚洲国产精品毛片在线看| 国产无遮挡又黄又爽不要vip软件 国产成人精品一区二区秒拍1o | 成 人色 网 站 欧美大片| 成人一区二区人妻不卡视频| 天堂一区二区三区av| 午夜福利国产盗摄久久性| 国精偷拍一区二区三区| 午夜国产理论大片高清| 婷婷99视频精品全部在线观看| 夜夜爽免费888视频| 国产亚洲精品久久久久久久久| 国产做无码视频在线观看浪潮| 线观看的国产成人av天堂| 婷婷久久综合九色综合88| 国产成人亚洲欧美二区综合| 久久精品人人槡人妻人人玩av| 无码专区 人妻系列 在线| 日本三级理论久久人妻电影| 日韩国产成人精品视频| 婷婷四房综合激情五月在线| 国产精品午夜av福利|