mirror of
				https://github.com/gopl-zh/gopl-zh.github.com.git
				synced 2025-10-31 10:11:37 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			480 lines
		
	
	
		
			42 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			480 lines
		
	
	
		
			42 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
| <!DOCTYPE HTML>
 | ||
| <html lang="zh" class="sidebar-visible no-js light">
 | ||
|     <head>
 | ||
|         <!-- Book generated using mdBook -->
 | ||
|         <meta charset="UTF-8">
 | ||
|         <title>匿名函数 - Go语言圣经</title>
 | ||
|                 
 | ||
| 
 | ||
|         <!-- Custom HTML head -->
 | ||
|         
 | ||
| 
 | ||
|         <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
 | ||
|         <meta name="description" content="<The Go Programming Language>中文版">
 | ||
|         <meta name="viewport" content="width=device-width, initial-scale=1">
 | ||
|         <meta name="theme-color" content="#ffffff" />
 | ||
| 
 | ||
|                 <link rel="icon" href="../favicon.svg">
 | ||
|                         <link rel="shortcut icon" href="../favicon.png">
 | ||
|                 <link rel="stylesheet" href="../css/variables.css">
 | ||
|         <link rel="stylesheet" href="../css/general.css">
 | ||
|         <link rel="stylesheet" href="../css/chrome.css">
 | ||
|                 <link rel="stylesheet" href="../css/print.css" media="print">
 | ||
|         
 | ||
|         <!-- Fonts -->
 | ||
|         <link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
 | ||
|                 <link rel="stylesheet" href="../fonts/fonts.css">
 | ||
|         
 | ||
|         <!-- Highlight.js Stylesheets -->
 | ||
|         <link rel="stylesheet" href="../highlight.css">
 | ||
|         <link rel="stylesheet" href="../tomorrow-night.css">
 | ||
|         <link rel="stylesheet" href="../ayu-highlight.css">
 | ||
| 
 | ||
|         <!-- Custom theme stylesheets -->
 | ||
|                 <link rel="stylesheet" href="../style.css">
 | ||
|         
 | ||
|         
 | ||
|     </head>
 | ||
|     <body>
 | ||
|         <!-- Provide site root to javascript -->
 | ||
|         <script type="text/javascript">
 | ||
|             var path_to_root = "../";
 | ||
|             var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
 | ||
|         </script>
 | ||
| 
 | ||
|         <!-- Work around some values being stored in localStorage wrapped in quotes -->
 | ||
|         <script type="text/javascript">
 | ||
|             try {
 | ||
|                 var theme = localStorage.getItem('mdbook-theme');
 | ||
|                 var sidebar = localStorage.getItem('mdbook-sidebar');
 | ||
| 
 | ||
|                 if (theme.startsWith('"') && theme.endsWith('"')) {
 | ||
|                     localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
 | ||
|                 }
 | ||
| 
 | ||
|                 if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
 | ||
|                     localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
 | ||
|                 }
 | ||
|             } catch (e) { }
 | ||
|         </script>
 | ||
| 
 | ||
|         <!-- Set the theme before any content is loaded, prevents flash -->
 | ||
|         <script type="text/javascript">
 | ||
|             var theme;
 | ||
|             try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
 | ||
|             if (theme === null || theme === undefined) { theme = default_theme; }
 | ||
|             var html = document.querySelector('html');
 | ||
|             html.classList.remove('no-js')
 | ||
|             html.classList.remove('light')
 | ||
|             html.classList.add(theme);
 | ||
|             html.classList.add('js');
 | ||
|         </script>
 | ||
| 
 | ||
|         <!-- Hide / unhide sidebar before it is displayed -->
 | ||
|         <script type="text/javascript">
 | ||
|             var html = document.querySelector('html');
 | ||
|             var sidebar = 'hidden';
 | ||
|             if (document.body.clientWidth >= 1080) {
 | ||
|                 try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
 | ||
|                 sidebar = sidebar || 'visible';
 | ||
|             }
 | ||
|             html.classList.remove('sidebar-visible');
 | ||
|             html.classList.add("sidebar-" + sidebar);
 | ||
|         </script>
 | ||
| 
 | ||
|         <nav id="sidebar" class="sidebar" aria-label="Table of contents">
 | ||
|             <div class="sidebar-scrollbox">
 | ||
|                 <ol class="chapter"><li class="chapter-item expanded affix "><a href="../index.html">Go语言圣经</a></li><li class="chapter-item expanded affix "><a href="../preface-zh.html">译者序</a></li><li class="chapter-item expanded affix "><a href="../preface.html">前言</a></li><li class="chapter-item expanded "><a href="../ch1/ch1.html"><strong aria-hidden="true">1.</strong> 入门</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../ch1/ch1-01.html"><strong aria-hidden="true">1.1.</strong> Hello, World</a></li><li class="chapter-item expanded "><a href="../ch1/ch1-02.html"><strong aria-hidden="true">1.2.</strong> 命令行参数</a></li><li class="chapter-item expanded "><a href="../ch1/ch1-03.html"><strong aria-hidden="true">1.3.</strong> 查找重复的行</a></li><li class="chapter-item expanded "><a href="../ch1/ch1-04.html"><strong aria-hidden="true">1.4.</strong> GIF动画</a></li><li class="chapter-item expanded "><a href="../ch1/ch1-05.html"><strong aria-hidden="true">1.5.</strong> 获取URL</a></li><li class="chapter-item expanded "><a href="../ch1/ch1-06.html"><strong aria-hidden="true">1.6.</strong> 并发获取多个URL</a></li><li class="chapter-item expanded "><a href="../ch1/ch1-07.html"><strong aria-hidden="true">1.7.</strong> Web服务</a></li><li class="chapter-item expanded "><a href="../ch1/ch1-08.html"><strong aria-hidden="true">1.8.</strong> 本章要点</a></li></ol></li><li class="chapter-item expanded "><a href="../ch2/ch2.html"><strong aria-hidden="true">2.</strong> 程序结构</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../ch2/ch2-01.html"><strong aria-hidden="true">2.1.</strong> 命名</a></li><li class="chapter-item expanded "><a href="../ch2/ch2-02.html"><strong aria-hidden="true">2.2.</strong> 声明</a></li><li class="chapter-item expanded "><a href="../ch2/ch2-03.html"><strong aria-hidden="true">2.3.</strong> 变量</a></li><li class="chapter-item expanded "><a href="../ch2/ch2-04.html"><strong aria-hidden="true">2.4.</strong> 赋值</a></li><li class="chapter-item expanded "><a href="../ch2/ch2-05.html"><strong aria-hidden="true">2.5.</strong> 类型</a></li><li class="chapter-item expanded "><a href="../ch2/ch2-06.html"><strong aria-hidden="true">2.6.</strong> 包和文件</a></li><li class="chapter-item expanded "><a href="../ch2/ch2-07.html"><strong aria-hidden="true">2.7.</strong> 作用域</a></li></ol></li><li class="chapter-item expanded "><a href="../ch3/ch3.html"><strong aria-hidden="true">3.</strong> 基础数据类型</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../ch3/ch3-01.html"><strong aria-hidden="true">3.1.</strong> 整型</a></li><li class="chapter-item expanded "><a href="../ch3/ch3-02.html"><strong aria-hidden="true">3.2.</strong> 浮点数</a></li><li class="chapter-item expanded "><a href="../ch3/ch3-03.html"><strong aria-hidden="true">3.3.</strong> 复数</a></li><li class="chapter-item expanded "><a href="../ch3/ch3-04.html"><strong aria-hidden="true">3.4.</strong> 布尔型</a></li><li class="chapter-item expanded "><a href="../ch3/ch3-05.html"><strong aria-hidden="true">3.5.</strong> 字符串</a></li><li class="chapter-item expanded "><a href="../ch3/ch3-06.html"><strong aria-hidden="true">3.6.</strong> 常量</a></li></ol></li><li class="chapter-item expanded "><a href="../ch4/ch4.html"><strong aria-hidden="true">4.</strong> 复合数据类型</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../ch4/ch4-01.html"><strong aria-hidden="true">4.1.</strong> 数组</a></li><li class="chapter-item expanded "><a href="../ch4/ch4-02.html"><strong aria-hidden="true">4.2.</strong> Slice</a></li><li class="chapter-item expanded "><a href="../ch4/ch4-03.html"><strong aria-hidden="true">4.3.</strong> Map</a></li><li class="chapter-item expanded "><a href="../ch4/ch4-04.html"><strong aria-hidden="true">4.4.</strong> 结构体</a></li><li class="chapter-item expanded "><a href="../ch4/ch4-05.html"><strong aria-hidden="true">4.5.</strong> JSON</a></li><li class="chapter-item expanded "><a href="../ch4/ch4-06.html"><strong aria-hidden="true">4.6.</strong> 文本和HTML模板</a></li></ol></li><li class="chapter-item expanded "><a href="../ch5/ch5.html"><strong aria-hidden="true">5.</strong> 函数</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../ch5/ch5-01.html"><strong aria-hidden="true">5.1.</strong> 函数声明</a></li><li class="chapter-item expanded "><a href="../ch5/ch5-02.html"><strong aria-hidden="true">5.2.</strong> 递归</a></li><li class="chapter-item expanded "><a href="../ch5/ch5-03.html"><strong aria-hidden="true">5.3.</strong> 多返回值</a></li><li class="chapter-item expanded "><a href="../ch5/ch5-04.html"><strong aria-hidden="true">5.4.</strong> 错误</a></li><li class="chapter-item expanded "><a href="../ch5/ch5-05.html"><strong aria-hidden="true">5.5.</strong> 函数值</a></li><li class="chapter-item expanded "><a href="../ch5/ch5-06.html" class="active"><strong aria-hidden="true">5.6.</strong> 匿名函数</a></li><li class="chapter-item expanded "><a href="../ch5/ch5-07.html"><strong aria-hidden="true">5.7.</strong> 可变参数</a></li><li class="chapter-item expanded "><a href="../ch5/ch5-08.html"><strong aria-hidden="true">5.8.</strong> Deferred函数</a></li><li class="chapter-item expanded "><a href="../ch5/ch5-09.html"><strong aria-hidden="true">5.9.</strong> Panic异常</a></li><li class="chapter-item expanded "><a href="../ch5/ch5-10.html"><strong aria-hidden="true">5.10.</strong> Recover捕获异常</a></li></ol></li><li class="chapter-item expanded "><a href="../ch6/ch6.html"><strong aria-hidden="true">6.</strong> 方法</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../ch6/ch6-01.html"><strong aria-hidden="true">6.1.</strong> 方法声明</a></li><li class="chapter-item expanded "><a href="../ch6/ch6-02.html"><strong aria-hidden="true">6.2.</strong> 基于指针对象的方法</a></li><li class="chapter-item expanded "><a href="../ch6/ch6-03.html"><strong aria-hidden="true">6.3.</strong> 通过嵌入结构体来扩展类型</a></li><li class="chapter-item expanded "><a href="../ch6/ch6-04.html"><strong aria-hidden="true">6.4.</strong> 方法值和方法表达式</a></li><li class="chapter-item expanded "><a href="../ch6/ch6-05.html"><strong aria-hidden="true">6.5.</strong> 示例: Bit数组</a></li><li class="chapter-item expanded "><a href="../ch6/ch6-06.html"><strong aria-hidden="true">6.6.</strong> 封装</a></li></ol></li><li class="chapter-item expanded "><a href="../ch7/ch7.html"><strong aria-hidden="true">7.</strong> 接口</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../ch7/ch7-01.html"><strong aria-hidden="true">7.1.</strong> 接口是合约</a></li><li class="chapter-item expanded "><a href="../ch7/ch7-02.html"><strong aria-hidden="true">7.2.</strong> 接口类型</a></li><li class="chapter-item expanded "><a href="../ch7/ch7-03.html"><strong aria-hidden="true">7.3.</strong> 实现接口的条件</a></li><li class="chapter-item expanded "><a href="../ch7/ch7-04.html"><strong aria-hidden="true">7.4.</strong> flag.Value接口</a></li><li class="chapter-item expanded "><a href="../ch7/ch7-05.html"><strong aria-hidden="true">7.5.</strong> 接口值</a></li><li class="chapter-item expanded "><a href="../ch7/ch7-06.html"><strong aria-hidden="true">7.6.</strong> sort.Interface接口</a></li><li class="chapter-item expanded "><a href="../ch7/ch7-07.html"><strong aria-hidden="true">7.7.</strong> http.Handler接口</a></li><li class="chapter-item expanded "><a href="../ch7/ch7-08.html"><strong aria-hidden="true">7.8.</strong> error接口</a></li><li class="chapter-item expanded "><a href="../ch7/ch7-09.html"><strong aria-hidden="true">7.9.</strong> 示例: 表达式求值</a></li><li class="chapter-item expanded "><a href="../ch7/ch7-10.html"><strong aria-hidden="true">7.10.</strong> 类型断言</a></li><li class="chapter-item expanded "><a href="../ch7/ch7-11.html"><strong aria-hidden="true">7.11.</strong> 基于类型断言识别错误类型</a></li><li class="chapter-item expanded "><a href="../ch7/ch7-12.html"><strong aria-hidden="true">7.12.</strong> 通过类型断言查询接口</a></li><li class="chapter-item expanded "><a href="../ch7/ch7-13.html"><strong aria-hidden="true">7.13.</strong> 类型分支</a></li><li class="chapter-item expanded "><a href="../ch7/ch7-14.html"><strong aria-hidden="true">7.14.</strong> 示例: 基于标记的XML解码</a></li><li class="chapter-item expanded "><a href="../ch7/ch7-15.html"><strong aria-hidden="true">7.15.</strong> 补充几点</a></li></ol></li><li class="chapter-item expanded "><a href="../ch8/ch8.html"><strong aria-hidden="true">8.</strong> Goroutines和Channels</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../ch8/ch8-01.html"><strong aria-hidden="true">8.1.</strong> Goroutines</a></li><li class="chapter-item expanded "><a href="../ch8/ch8-02.html"><strong aria-hidden="true">8.2.</strong> 示例: 并发的Clock服务</a></li><li class="chapter-item expanded "><a href="../ch8/ch8-03.html"><strong aria-hidden="true">8.3.</strong> 示例: 并发的Echo服务</a></li><li class="chapter-item expanded "><a href="../ch8/ch8-04.html"><strong aria-hidden="true">8.4.</strong> Channels</a></li><li class="chapter-item expanded "><a href="../ch8/ch8-05.html"><strong aria-hidden="true">8.5.</strong> 并发的循环</a></li><li class="chapter-item expanded "><a href="../ch8/ch8-06.html"><strong aria-hidden="true">8.6.</strong> 示例: 并发的Web爬虫</a></li><li class="chapter-item expanded "><a href="../ch8/ch8-07.html"><strong aria-hidden="true">8.7.</strong> 基于select的多路复用</a></li><li class="chapter-item expanded "><a href="../ch8/ch8-08.html"><strong aria-hidden="true">8.8.</strong> 示例: 并发的目录遍历</a></li><li class="chapter-item expanded "><a href="../ch8/ch8-09.html"><strong aria-hidden="true">8.9.</strong> 并发的退出</a></li><li class="chapter-item expanded "><a href="../ch8/ch8-10.html"><strong aria-hidden="true">8.10.</strong> 示例: 聊天服务</a></li></ol></li><li class="chapter-item expanded "><a href="../ch9/ch9.html"><strong aria-hidden="true">9.</strong> 基于共享变量的并发</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../ch9/ch9-01.html"><strong aria-hidden="true">9.1.</strong> 竞争条件</a></li><li class="chapter-item expanded "><a href="../ch9/ch9-02.html"><strong aria-hidden="true">9.2.</strong> sync.Mutex互斥锁</a></li><li class="chapter-item expanded "><a href="../ch9/ch9-03.html"><strong aria-hidden="true">9.3.</strong> sync.RWMutex读写锁</a></li><li class="chapter-item expanded "><a href="../ch9/ch9-04.html"><strong aria-hidden="true">9.4.</strong> 内存同步</a></li><li class="chapter-item expanded "><a href="../ch9/ch9-05.html"><strong aria-hidden="true">9.5.</strong> sync.Once惰性初始化</a></li><li class="chapter-item expanded "><a href="../ch9/ch9-06.html"><strong aria-hidden="true">9.6.</strong> 竞争条件检测</a></li><li class="chapter-item expanded "><a href="../ch9/ch9-07.html"><strong aria-hidden="true">9.7.</strong> 示例: 并发的非阻塞缓存</a></li><li class="chapter-item expanded "><a href="../ch9/ch9-08.html"><strong aria-hidden="true">9.8.</strong> Goroutines和线程</a></li></ol></li><li class="chapter-item expanded "><a href="../ch10/ch10.html"><strong aria-hidden="true">10.</strong> 包和工具</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../ch10/ch10-01.html"><strong aria-hidden="true">10.1.</strong> 包简介</a></li><li class="chapter-item expanded "><a href="../ch10/ch10-02.html"><strong aria-hidden="true">10.2.</strong> 导入路径</a></li><li class="chapter-item expanded "><a href="../ch10/ch10-03.html"><strong aria-hidden="true">10.3.</strong> 包声明</a></li><li class="chapter-item expanded "><a href="../ch10/ch10-04.html"><strong aria-hidden="true">10.4.</strong> 导入声明</a></li><li class="chapter-item expanded "><a href="../ch10/ch10-05.html"><strong aria-hidden="true">10.5.</strong> 包的匿名导入</a></li><li class="chapter-item expanded "><a href="../ch10/ch10-06.html"><strong aria-hidden="true">10.6.</strong> 包和命名</a></li><li class="chapter-item expanded "><a href="../ch10/ch10-07.html"><strong aria-hidden="true">10.7.</strong> 工具</a></li></ol></li><li class="chapter-item expanded "><a href="../ch11/ch11.html"><strong aria-hidden="true">11.</strong> 测试</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../ch11/ch11-01.html"><strong aria-hidden="true">11.1.</strong> go test</a></li><li class="chapter-item expanded "><a href="../ch11/ch11-02.html"><strong aria-hidden="true">11.2.</strong> 测试函数</a></li><li class="chapter-item expanded "><a href="../ch11/ch11-03.html"><strong aria-hidden="true">11.3.</strong> 测试覆盖率</a></li><li class="chapter-item expanded "><a href="../ch11/ch11-04.html"><strong aria-hidden="true">11.4.</strong> 基准测试</a></li><li class="chapter-item expanded "><a href="../ch11/ch11-05.html"><strong aria-hidden="true">11.5.</strong> 剖析</a></li><li class="chapter-item expanded "><a href="../ch11/ch11-06.html"><strong aria-hidden="true">11.6.</strong> 示例函数</a></li></ol></li><li class="chapter-item expanded "><a href="../ch12/ch12.html"><strong aria-hidden="true">12.</strong> 反射</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../ch12/ch12-01.html"><strong aria-hidden="true">12.1.</strong> 为何需要反射?</a></li><li class="chapter-item expanded "><a href="../ch12/ch12-02.html"><strong aria-hidden="true">12.2.</strong> reflect.Type和reflect.Value</a></li><li class="chapter-item expanded "><a href="../ch12/ch12-03.html"><strong aria-hidden="true">12.3.</strong> Display递归打印</a></li><li class="chapter-item expanded "><a href="../ch12/ch12-04.html"><strong aria-hidden="true">12.4.</strong> 示例: 编码S表达式</a></li><li class="chapter-item expanded "><a href="../ch12/ch12-05.html"><strong aria-hidden="true">12.5.</strong> 通过reflect.Value修改值</a></li><li class="chapter-item expanded "><a href="../ch12/ch12-06.html"><strong aria-hidden="true">12.6.</strong> 示例: 解码S表达式</a></li><li class="chapter-item expanded "><a href="../ch12/ch12-07.html"><strong aria-hidden="true">12.7.</strong> 获取结构体字段标签</a></li><li class="chapter-item expanded "><a href="../ch12/ch12-08.html"><strong aria-hidden="true">12.8.</strong> 显示一个类型的方法集</a></li><li class="chapter-item expanded "><a href="../ch12/ch12-09.html"><strong aria-hidden="true">12.9.</strong> 几点忠告</a></li></ol></li><li class="chapter-item expanded "><a href="../ch13/ch13.html"><strong aria-hidden="true">13.</strong> 底层编程</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../ch13/ch13-01.html"><strong aria-hidden="true">13.1.</strong> unsafe.Sizeof, Alignof 和 Offsetof</a></li><li class="chapter-item expanded "><a href="../ch13/ch13-02.html"><strong aria-hidden="true">13.2.</strong> unsafe.Pointer</a></li><li class="chapter-item expanded "><a href="../ch13/ch13-03.html"><strong aria-hidden="true">13.3.</strong> 示例: 深度相等判断</a></li><li class="chapter-item expanded "><a href="../ch13/ch13-04.html"><strong aria-hidden="true">13.4.</strong> 通过cgo调用C代码</a></li><li class="chapter-item expanded "><a href="../ch13/ch13-05.html"><strong aria-hidden="true">13.5.</strong> 几点忠告</a></li></ol></li><li class="chapter-item expanded "><a href="../appendix/appendix.html"><strong aria-hidden="true">14.</strong> 附录</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../appendix/appendix-a-errata.html"><strong aria-hidden="true">14.1.</strong> 附录A:原文勘误</a></li><li class="chapter-item expanded "><a href="../appendix/appendix-b-author.html"><strong aria-hidden="true">14.2.</strong> 附录B:作者译者</a></li><li class="chapter-item expanded "><a href="../appendix/appendix-c-cpoyright.html"><strong aria-hidden="true">14.3.</strong> 附录C:译文授权</a></li><li class="chapter-item expanded "><a href="../appendix/appendix-d-translations.html"><strong aria-hidden="true">14.4.</strong> 附录D:其它语言</a></li></ol></li></ol>            </div>
 | ||
|             <div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
 | ||
|         </nav>
 | ||
| 
 | ||
|         <div id="page-wrapper" class="page-wrapper">
 | ||
| 
 | ||
|             <div class="page">
 | ||
|                 
 | ||
|                 <div id="menu-bar-hover-placeholder"></div>
 | ||
|                 <div id="menu-bar" class="menu-bar sticky bordered">
 | ||
|                     <div class="left-buttons">
 | ||
|                         <button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
 | ||
|                             <i class="fa fa-bars"></i>
 | ||
|                         </button>
 | ||
|                         <button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
 | ||
|                             <i class="fa fa-paint-brush"></i>
 | ||
|                         </button>
 | ||
|                         <ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
 | ||
|                             <li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
 | ||
|                             <li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
 | ||
|                             <li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
 | ||
|                             <li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
 | ||
|                             <li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
 | ||
|                         </ul>
 | ||
|                                                 <button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
 | ||
|                             <i class="fa fa-search"></i>
 | ||
|                         </button>
 | ||
|                                             </div>
 | ||
| 
 | ||
|                     <h1 class="menu-title">Go语言圣经</h1>
 | ||
| 
 | ||
|                     <div class="right-buttons">
 | ||
|                                                 <a href="../print.html" title="Print this book" aria-label="Print this book">
 | ||
|                             <i id="print-button" class="fa fa-print"></i>
 | ||
|                         </a>
 | ||
|                                                                         <a href="https://github.com/gopl-zh/gopl-zh.github.com" title="Git repository" aria-label="Git repository">
 | ||
|                             <i id="git-repository-button" class="fa fa-github"></i>
 | ||
|                         </a>
 | ||
|                                                                         <a href="https://github.com/gopl-zh/gopl-zh.github.com/edit/master/./ch5/ch5-06.md" title="Suggest an edit" aria-label="Suggest an edit">
 | ||
|                             <i id="git-edit-button" class="fa fa-edit"></i>
 | ||
|                         </a>
 | ||
|                         
 | ||
|                     </div>
 | ||
|                 </div>
 | ||
| 
 | ||
|                                 <div id="search-wrapper" class="hidden">
 | ||
|                     <form id="searchbar-outer" class="searchbar-outer">
 | ||
|                         <input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
 | ||
|                     </form>
 | ||
|                     <div id="searchresults-outer" class="searchresults-outer hidden">
 | ||
|                         <div id="searchresults-header" class="searchresults-header"></div>
 | ||
|                         <ul id="searchresults">
 | ||
|                         </ul>
 | ||
|                     </div>
 | ||
|                 </div>
 | ||
|                 
 | ||
|                 <!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
 | ||
|                 <script type="text/javascript">
 | ||
|                     document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
 | ||
|                     document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
 | ||
|                     Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
 | ||
|                         link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
 | ||
|                     });
 | ||
|                 </script>
 | ||
| 
 | ||
|                 <div id="content" class="content">
 | ||
|                     <!-- Page table of contents -->
 | ||
|                     <div class="sidetoc"><nav class="pagetoc"></nav></div>
 | ||
|                     <main>
 | ||
|                         <!-- 头部 -->
 | ||
|                         <ul dir="auto">
 | ||
|                         <li><em>凹语言(专为 WebAssembly 设计): <a href="https://github.com/wa-lang/wa">https://github.com/wa-lang/wa</a></em></li>
 | ||
|                         <li><em>KCL 配置语言(Rust): <a href="https://github.com/kcl-lang/kcl">https://github.com/kcl-lang/kcl</a></em></li>
 | ||
| 			</ul>
 | ||
|                         <hr>
 | ||
| 
 | ||
|                         <h2 id="56-匿名函数"><a class="header" href="#56-匿名函数">5.6. 匿名函数</a></h2>
 | ||
| <p>拥有函数名的函数只能在包级语法块中被声明,通过函数字面量(function literal),我们可绕过这一限制,在任何表达式中表示一个函数值。函数字面量的语法和函数声明相似,区别在于func关键字后没有函数名。函数值字面量是一种表达式,它的值被称为匿名函数(anonymous function)。</p>
 | ||
| <p>函数字面量允许我们在使用函数时,再定义它。通过这种技巧,我们可以改写之前对strings.Map的调用:</p>
 | ||
| <pre><code class="language-Go">strings.Map(func(r rune) rune { return r + 1 }, "HAL-9000")
 | ||
| </code></pre>
 | ||
| <p>更为重要的是,通过这种方式定义的函数可以访问完整的词法环境(lexical environment),这意味着在函数中定义的内部函数可以引用该函数的变量,如下例所示:</p>
 | ||
| <p><u><i>gopl.io/ch5/squares</i></u></p>
 | ||
| <pre><code class="language-Go">// squares返回一个匿名函数。
 | ||
| // 该匿名函数每次被调用时都会返回下一个数的平方。
 | ||
| func squares() func() int {
 | ||
| 	var x int
 | ||
| 	return func() int {
 | ||
| 		x++
 | ||
| 		return x * x
 | ||
| 	}
 | ||
| }
 | ||
| func main() {
 | ||
| 	f := squares()
 | ||
| 	fmt.Println(f()) // "1"
 | ||
| 	fmt.Println(f()) // "4"
 | ||
| 	fmt.Println(f()) // "9"
 | ||
| 	fmt.Println(f()) // "16"
 | ||
| }
 | ||
| </code></pre>
 | ||
| <p>函数squares返回另一个类型为 func() int 的函数。对squares的一次调用会生成一个局部变量x并返回一个匿名函数。每次调用匿名函数时,该函数都会先使x的值加1,再返回x的平方。第二次调用squares时,会生成第二个x变量,并返回一个新的匿名函数。新匿名函数操作的是第二个x变量。</p>
 | ||
| <p>squares的例子证明,函数值不仅仅是一串代码,还记录了状态。在squares中定义的匿名内部函数可以访问和更新squares中的局部变量,这意味着匿名函数和squares中,存在变量引用。这就是函数值属于引用类型和函数值不可比较的原因。Go使用闭包(closures)技术实现函数值,Go程序员也把函数值叫做闭包。</p>
 | ||
| <p>通过这个例子,我们看到变量的生命周期不由它的作用域决定:squares返回后,变量x仍然隐式的存在于f中。</p>
 | ||
| <p>接下来,我们讨论一个有点学术性的例子,考虑这样一个问题:给定一些计算机课程,每个课程都有前置课程,只有完成了前置课程才可以开始当前课程的学习;我们的目标是选择出一组课程,这组课程必须确保按顺序学习时,能全部被完成。每个课程的前置课程如下:</p>
 | ||
| <p><u><i>gopl.io/ch5/toposort</i></u></p>
 | ||
| <pre><code class="language-Go">// prereqs记录了每个课程的前置课程
 | ||
| var prereqs = map[string][]string{
 | ||
| 	"algorithms": {"data structures"},
 | ||
| 	"calculus": {"linear algebra"},
 | ||
| 	"compilers": {
 | ||
| 		"data structures",
 | ||
| 		"formal languages",
 | ||
| 		"computer organization",
 | ||
| 	},
 | ||
| 	"data structures":       {"discrete math"},
 | ||
| 	"databases":             {"data structures"},
 | ||
| 	"discrete math":         {"intro to programming"},
 | ||
| 	"formal languages":      {"discrete math"},
 | ||
| 	"networks":              {"operating systems"},
 | ||
| 	"operating systems":     {"data structures", "computer organization"},
 | ||
| 	"programming languages": {"data structures", "computer organization"},
 | ||
| }
 | ||
| </code></pre>
 | ||
| <p>这类问题被称作拓扑排序。从概念上说,前置条件可以构成有向图。图中的顶点表示课程,边表示课程间的依赖关系。显然,图中应该无环,这也就是说从某点出发的边,最终不会回到该点。下面的代码用深度优先搜索了整张图,获得了符合要求的课程序列。</p>
 | ||
| <pre><code class="language-Go">func main() {
 | ||
| 	for i, course := range topoSort(prereqs) {
 | ||
| 		fmt.Printf("%d:\t%s\n", i+1, course)
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func topoSort(m map[string][]string) []string {
 | ||
| 	var order []string
 | ||
| 	seen := make(map[string]bool)
 | ||
| 	var visitAll func(items []string)
 | ||
| 	visitAll = func(items []string) {
 | ||
| 		for _, item := range items {
 | ||
| 			if !seen[item] {
 | ||
| 				seen[item] = true
 | ||
| 				visitAll(m[item])
 | ||
| 				order = append(order, item)
 | ||
| 			}
 | ||
| 		}
 | ||
| 	}
 | ||
| 	var keys []string
 | ||
| 	for key := range m {
 | ||
| 		keys = append(keys, key)
 | ||
| 	}
 | ||
| 	sort.Strings(keys)
 | ||
| 	visitAll(keys)
 | ||
| 	return order
 | ||
| }
 | ||
| </code></pre>
 | ||
| <p>当匿名函数需要被递归调用时,我们必须首先声明一个变量(在上面的例子中,我们首先声明了 visitAll),再将匿名函数赋值给这个变量。如果不分成两步,函数字面量无法与visitAll绑定,我们也无法递归调用该匿名函数。</p>
 | ||
| <pre><code class="language-Go">visitAll := func(items []string) {
 | ||
| 	// ...
 | ||
| 	visitAll(m[item]) // compile error: undefined: visitAll
 | ||
| 	// ...
 | ||
| }
 | ||
| </code></pre>
 | ||
| <p>在toposort程序的输出如下所示,它的输出顺序是大多人想看到的固定顺序输出,但是这需要我们多花点心思才能做到。哈希表prepreqs的value是遍历顺序固定的切片,而不再试遍历顺序随机的map,所以我们对prereqs的key值进行排序,保证每次运行toposort程序,都以相同的遍历顺序遍历prereqs。</p>
 | ||
| <pre><code>1: intro to programming
 | ||
| 2: discrete math
 | ||
| 3: data structures
 | ||
| 4: algorithms
 | ||
| 5: linear algebra
 | ||
| 6: calculus
 | ||
| 7: formal languages
 | ||
| 8: computer organization
 | ||
| 9: compilers
 | ||
| 10: databases
 | ||
| 11: operating systems
 | ||
| 12: networks
 | ||
| 13: programming languages
 | ||
| </code></pre>
 | ||
| <p>让我们回到findLinks这个例子。我们将代码移动到了links包下,将函数重命名为Extract,在第八章我们会再次用到这个函数。新的匿名函数被引入,用于替换原来的visit函数。该匿名函数负责将新连接添加到切片中。在Extract中,使用forEachNode遍历HTML页面,由于Extract只需要在遍历结点前操作结点,所以forEachNode的post参数被传入nil。</p>
 | ||
| <p><u><i>gopl.io/ch5/links</i></u></p>
 | ||
| <pre><code class="language-Go">// Package links provides a link-extraction function.
 | ||
| package links
 | ||
| import (
 | ||
| 	"fmt"
 | ||
| 	"net/http"
 | ||
| 	"golang.org/x/net/html"
 | ||
| )
 | ||
| // Extract makes an HTTP GET request to the specified URL, parses
 | ||
| // the response as HTML, and returns the links in the HTML document.
 | ||
| func Extract(url string) ([]string, error) {
 | ||
| 	resp, err := http.Get(url)
 | ||
| 	if err != nil {
 | ||
| 		return nil, err
 | ||
| 	}
 | ||
| 	if resp.StatusCode != http.StatusOK {
 | ||
| 	resp.Body.Close()
 | ||
| 		return nil, fmt.Errorf("getting %s: %s", url, resp.Status)
 | ||
| 	}
 | ||
| 	doc, err := html.Parse(resp.Body)
 | ||
| 	resp.Body.Close()
 | ||
| 	if err != nil {
 | ||
| 		return nil, fmt.Errorf("parsing %s as HTML: %v", url, err)
 | ||
| 	}
 | ||
| 	var links []string
 | ||
| 	visitNode := func(n *html.Node) {
 | ||
| 		if n.Type == html.ElementNode && n.Data == "a" {
 | ||
| 			for _, a := range n.Attr {
 | ||
| 				if a.Key != "href" {
 | ||
| 					continue
 | ||
| 				}
 | ||
| 				link, err := resp.Request.URL.Parse(a.Val)
 | ||
| 				if err != nil {
 | ||
| 					continue // ignore bad URLs
 | ||
| 				}
 | ||
| 				links = append(links, link.String())
 | ||
| 			}
 | ||
| 		}
 | ||
| 	}
 | ||
| 	forEachNode(doc, visitNode, nil)
 | ||
| 	return links, nil
 | ||
| }
 | ||
| </code></pre>
 | ||
| <p>上面的代码对之前的版本做了改进,现在links中存储的不是href属性的原始值,而是通过resp.Request.URL解析后的值。解析后,这些连接以绝对路径的形式存在,可以直接被http.Get访问。</p>
 | ||
| <p>网页抓取的核心问题就是如何遍历图。在topoSort的例子中,已经展示了深度优先遍历,在网页抓取中,我们会展示如何用广度优先遍历图。在第8章,我们会介绍如何将深度优先和广度优先结合使用。</p>
 | ||
| <p>下面的函数实现了广度优先算法。调用者需要输入一个初始的待访问列表和一个函数f。待访问列表中的每个元素被定义为string类型。广度优先算法会为每个元素调用一次f。每次f执行完毕后,会返回一组待访问元素。这些元素会被加入到待访问列表中。当待访问列表中的所有元素都被访问后,breadthFirst函数运行结束。为了避免同一个元素被访问两次,代码中维护了一个map。</p>
 | ||
| <p><u><i>gopl.io/ch5/findlinks3</i></u></p>
 | ||
| <pre><code class="language-Go">// breadthFirst calls f for each item in the worklist.
 | ||
| // Any items returned by f are added to the worklist.
 | ||
| // f is called at most once for each item.
 | ||
| func breadthFirst(f func(item string) []string, worklist []string) {
 | ||
| 	seen := make(map[string]bool)
 | ||
| 	for len(worklist) > 0 {
 | ||
| 		items := worklist
 | ||
| 		worklist = nil
 | ||
| 		for _, item := range items {
 | ||
| 			if !seen[item] {
 | ||
| 				seen[item] = true
 | ||
| 				worklist = append(worklist, f(item)...)
 | ||
| 			}
 | ||
| 		}
 | ||
| 	}
 | ||
| }
 | ||
| </code></pre>
 | ||
| <p>就像我们在章节3解释的那样,append的参数“f(item)...”,会将f返回的一组元素一个个添加到worklist中。</p>
 | ||
| <p>在我们网页抓取器中,元素的类型是url。crawl函数会将URL输出,提取其中的新链接,并将这些新链接返回。我们会将crawl作为参数传递给breadthFirst。</p>
 | ||
| <pre><code class="language-go">func crawl(url string) []string {
 | ||
| 	fmt.Println(url)
 | ||
| 	list, err := links.Extract(url)
 | ||
| 	if err != nil {
 | ||
| 		log.Print(err)
 | ||
| 	}
 | ||
| 	return list
 | ||
| }
 | ||
| </code></pre>
 | ||
| <p>为了使抓取器开始运行,我们用命令行输入的参数作为初始的待访问url。</p>
 | ||
| <pre><code class="language-Go">func main() {
 | ||
| 	// Crawl the web breadth-first,
 | ||
| 	// starting from the command-line arguments.
 | ||
| 	breadthFirst(crawl, os.Args[1:])
 | ||
| }
 | ||
| </code></pre>
 | ||
| <p>让我们从 https://golang.org 开始,下面是程序的输出结果:</p>
 | ||
| <pre><code>$ go build gopl.io/ch5/findlinks3
 | ||
| $ ./findlinks3 https://golang.org
 | ||
| https://golang.org/
 | ||
| https://golang.org/doc/
 | ||
| https://golang.org/pkg/
 | ||
| https://golang.org/project/
 | ||
| https://code.google.com/p/go-tour/
 | ||
| https://golang.org/doc/code.html
 | ||
| https://www.youtube.com/watch?v=XCsL89YtqCs
 | ||
| http://research.swtch.com/gotour
 | ||
| </code></pre>
 | ||
| <p>当所有发现的链接都已经被访问或电脑的内存耗尽时,程序运行结束。</p>
 | ||
| <p><strong>练习5.10:</strong> 重写topoSort函数,用map代替切片并移除对key的排序代码。验证结果的正确性(结果不唯一)。</p>
 | ||
| <p><strong>练习5.11:</strong> 现在线性代数的老师把微积分设为了前置课程。完善topSort,使其能检测有向图中的环。</p>
 | ||
| <p><strong>练习5.12:</strong> gopl.io/ch5/outline2(5.5节)的startElement和endElement共用了全局变量depth,将它们修改为匿名函数,使其共享outline中的局部变量。</p>
 | ||
| <p><strong>练习5.13:</strong> 修改crawl,使其能保存发现的页面,必要时,可以创建目录来保存这些页面。只保存来自原始域名下的页面。假设初始页面在golang.org下,就不要保存vimeo.com下的页面。</p>
 | ||
| <p><strong>练习5.14:</strong> 使用breadthFirst遍历其他数据结构。比如,topoSort例子中的课程依赖关系(有向图)、个人计算机的文件层次结构(树);你所在城市的公交或地铁线路(无向图)。</p>
 | ||
| <h3 id="561-警告捕获迭代变量"><a class="header" href="#561-警告捕获迭代变量">5.6.1. 警告:捕获迭代变量</a></h3>
 | ||
| <p>本节,将介绍Go词法作用域的一个陷阱。请务必仔细的阅读,弄清楚发生问题的原因。即使是经验丰富的程序员也会在这个问题上犯错误。</p>
 | ||
| <p>考虑这样一个问题:你被要求首先创建一些目录,再将目录删除。在下面的例子中我们用函数值来完成删除操作。下面的示例代码需要引入os包。为了使代码简单,我们忽略了所有的异常处理。</p>
 | ||
| <pre><code class="language-Go">var rmdirs []func()
 | ||
| for _, d := range tempDirs() {
 | ||
| 	dir := d // NOTE: necessary!
 | ||
| 	os.MkdirAll(dir, 0755) // creates parent directories too
 | ||
| 	rmdirs = append(rmdirs, func() {
 | ||
| 		os.RemoveAll(dir)
 | ||
| 	})
 | ||
| }
 | ||
| // ...do some work…
 | ||
| for _, rmdir := range rmdirs {
 | ||
| 	rmdir() // clean up
 | ||
| }
 | ||
| </code></pre>
 | ||
| <p>你可能会感到困惑,为什么要在循环体中用循环变量d赋值一个新的局部变量,而不是像下面的代码一样直接使用循环变量dir。需要注意,下面的代码是错误的。</p>
 | ||
| <pre><code class="language-go">var rmdirs []func()
 | ||
| for _, dir := range tempDirs() {
 | ||
| 	os.MkdirAll(dir, 0755)
 | ||
| 	rmdirs = append(rmdirs, func() {
 | ||
| 		os.RemoveAll(dir) // NOTE: incorrect!
 | ||
| 	})
 | ||
| }
 | ||
| </code></pre>
 | ||
| <p>问题的原因在于循环变量的作用域。在上面的程序中,for循环语句引入了新的词法块,循环变量dir在这个词法块中被声明。在该循环中生成的所有函数值都共享相同的循环变量。需要注意,函数值中记录的是循环变量的内存地址,而不是循环变量某一时刻的值。以dir为例,后续的迭代会不断更新dir的值,当删除操作执行时,for循环已完成,dir中存储的值等于最后一次迭代的值。这意味着,每次对os.RemoveAll的调用删除的都是相同的目录。</p>
 | ||
| <p>通常,为了解决这个问题,我们会引入一个与循环变量同名的局部变量,作为循环变量的副本。比如下面的变量dir,虽然这看起来很奇怪,但却很有用。</p>
 | ||
| <pre><code class="language-Go">for _, dir := range tempDirs() {
 | ||
| 	dir := dir // declares inner dir, initialized to outer dir
 | ||
| 	// ...
 | ||
| }
 | ||
| </code></pre>
 | ||
| <p>这个问题不仅存在基于range的循环,在下面的例子中,对循环变量i的使用也存在同样的问题:</p>
 | ||
| <pre><code class="language-Go">var rmdirs []func()
 | ||
| dirs := tempDirs()
 | ||
| for i := 0; i < len(dirs); i++ {
 | ||
| 	os.MkdirAll(dirs[i], 0755) // OK
 | ||
| 	rmdirs = append(rmdirs, func() {
 | ||
| 		os.RemoveAll(dirs[i]) // NOTE: incorrect!
 | ||
| 	})
 | ||
| }
 | ||
| </code></pre>
 | ||
| <p>如果你使用go语句(第八章)或者defer语句(5.8节)会经常遇到此类问题。这不是go或defer本身导致的,而是因为它们都会等待循环结束后,再执行函数值。</p>
 | ||
| 
 | ||
| 
 | ||
|                         <!-- 公众号 -->
 | ||
|                         <hr>
 | ||
|                         <table>
 | ||
|                             <tr>
 | ||
|                             <td>
 | ||
|                             <img width="222px" src="https://chai2010.cn/advanced-go-programming-book/css.png">
 | ||
|                             </td>
 | ||
|                             <td>
 | ||
|                             <img width="222px" src="https://chai2010.cn/advanced-go-programming-book/cch.png">
 | ||
|                             </td>
 | ||
|                             </tr>
 | ||
|                         </table>
 | ||
| 
 | ||
|                         <div id="giscus-container"></div>
 | ||
|                         <footer class="page-footer">
 | ||
|                             <span>© 2015-2016 | <a href="https://github.com/gopl-zh"> Go语言圣经中文版</a>, 仅学习交流使用</span>
 | ||
|                         </footer>
 | ||
|                     </main>
 | ||
| 
 | ||
|                     <nav class="nav-wrapper" aria-label="Page navigation">
 | ||
|                         <!-- Mobile navigation buttons -->
 | ||
|                                                     <a rel="prev" href="../ch5/ch5-05.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
 | ||
|                                 <i class="fa fa-angle-left"></i>
 | ||
|                             </a>
 | ||
|                         
 | ||
|                                                     <a rel="next" href="../ch5/ch5-07.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
 | ||
|                                 <i class="fa fa-angle-right"></i>
 | ||
|                             </a>
 | ||
|                         
 | ||
|                         <div style="clear: both"></div>
 | ||
|                     </nav>
 | ||
|                 </div>
 | ||
|             </div>
 | ||
| 
 | ||
|             <nav class="nav-wide-wrapper" aria-label="Page navigation">
 | ||
|                                     <a rel="prev" href="../ch5/ch5-05.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
 | ||
|                         <i class="fa fa-angle-left"></i>
 | ||
|                     </a>
 | ||
|                 
 | ||
|                                     <a rel="next" href="../ch5/ch5-07.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
 | ||
|                         <i class="fa fa-angle-right"></i>
 | ||
|                     </a>
 | ||
|                             </nav>
 | ||
| 
 | ||
|         </div>
 | ||
| 
 | ||
|         
 | ||
|         
 | ||
|         
 | ||
|                 <script type="text/javascript">
 | ||
|             window.playground_copyable = true;
 | ||
|         </script>
 | ||
|         
 | ||
|         
 | ||
|                 <script src="../elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
 | ||
|         <script src="../mark.min.js" type="text/javascript" charset="utf-8"></script>
 | ||
|         <script src="../searcher.js" type="text/javascript" charset="utf-8"></script>
 | ||
|         
 | ||
|         <script src="../clipboard.min.js" type="text/javascript" charset="utf-8"></script>
 | ||
|         <script src="../highlight.js" type="text/javascript" charset="utf-8"></script>
 | ||
|         <script src="../book.js" type="text/javascript" charset="utf-8"></script>
 | ||
|         <script type="text/javascript" charset="utf-8">
 | ||
|         var pagePath = "ch5/ch5-06.md"
 | ||
|         </script>
 | ||
| 
 | ||
| 
 | ||
|         <!-- Custom JS scripts -->
 | ||
|                 <script type="text/javascript" src="../js/custom.js"></script>
 | ||
|                 <script type="text/javascript" src="../js/bigPicture.js"></script>
 | ||
|         
 | ||
|         
 | ||
|     </body>
 | ||
| </html>
 |