EffectiveModernCppChinese/3.MovingToModernCpp/item8.html
2023-04-22 03:34:15 +00:00

282 lines
26 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE HTML>
<html lang="zh" class="sidebar-visible no-js light">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Item 8:优先考虑nullptr而非0和NULL - Effective Modern C++</title>
<!-- Custom HTML head -->
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta name="description" content="">
<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 -->
<!-- MathJax -->
<script async type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
</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 "><a href="../Introduction.html">简介</a></li><li class="chapter-item expanded "><div>第一章 类型推导</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="../1.DeducingTypes/item1.html">Item 1:理解模板类型推导</a></li><li class="chapter-item expanded "><a href="../1.DeducingTypes/item2.html">Item 2:理解auto类型推导</a></li><li class="chapter-item expanded "><a href="../1.DeducingTypes/item3.html">Item 3:理解decltype</a></li><li class="chapter-item expanded "><a href="../1.DeducingTypes/item4.html">Item 4:学会查看类型推导结果</a></li></ol></li><li class="chapter-item expanded "><div>第二章 auto</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="../2.Auto/item5.html">Item 5:优先考虑auto而非显式类型声明</a></li><li class="chapter-item expanded "><a href="../2.Auto/item6.html">Item 6:auto推导若非己愿使用显式类型初始化惯用法</a></li></ol></li><li class="chapter-item expanded "><div>第三章 移步现代C++</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="../3.MovingToModernCpp/item7.html">Item 7:区别使用()和{}创建对象</a></li><li class="chapter-item expanded "><a href="../3.MovingToModernCpp/item8.html" class="active">Item 8:优先考虑nullptr而非0和NULL</a></li><li class="chapter-item expanded "><a href="../3.MovingToModernCpp/item9.html">Item 9:优先考虑别名声明而非typedefs</a></li><li class="chapter-item expanded "><a href="../3.MovingToModernCpp/item10.html">Item 10:优先考虑限域枚举而非未限域枚举</a></li><li class="chapter-item expanded "><a href="../3.MovingToModernCpp/item11.html">Item 11:优先考虑使用deleted函数而非使用未定义的私有声明</a></li><li class="chapter-item expanded "><a href="../3.MovingToModernCpp/item12.html">Item 12:使用override声明重载函数</a></li><li class="chapter-item expanded "><a href="../3.MovingToModernCpp/item13.html">Item 13:优先考虑const_iterator而非iterator</a></li><li class="chapter-item expanded "><a href="../3.MovingToModernCpp/item14.html">Item 14:如果函数不抛出异常请使用noexcept</a></li><li class="chapter-item expanded "><a href="../3.MovingToModernCpp/item15.html">Item 15:尽可能的使用constexpr</a></li><li class="chapter-item expanded "><a href="../3.MovingToModernCpp/item16.html">Item 16:让const成员函数线程安全</a></li><li class="chapter-item expanded "><a href="../3.MovingToModernCpp/item17.html">Item 17:理解特殊成员函数函数的生成</a></li></ol></li><li class="chapter-item expanded "><div>第四章 智能指针</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="../4.SmartPointers/item18.html">Item 18:对于独占资源使用std::unique_ptr</a></li><li class="chapter-item expanded "><a href="../4.SmartPointers/item19.html">Item 19:对于共享资源使用std::shared_ptr</a></li><li class="chapter-item expanded "><a href="../4.SmartPointers/item20.html">Item 20:当std::shared_ptr可能悬空时使用std::weak_ptr</a></li><li class="chapter-item expanded "><a href="../4.SmartPointers/item21.html">Item 21:优先考虑使用std::make_unique和std::make_shared而非new</a></li><li class="chapter-item expanded "><a href="../4.SmartPointers/item22.html">Item 22:当使用Pimpl惯用法请在实现文件中定义特殊成员函数</a></li></ol></li><li class="chapter-item expanded "><div>第五章 右值引用,移动语义,完美转发</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="../5.RRefMovSemPerfForw/item23.html">Item 23:理解std::move和std::forward</a></li><li class="chapter-item expanded "><a href="../5.RRefMovSemPerfForw/item24.html">Item 24:区别通用引用和右值引用</a></li><li class="chapter-item expanded "><a href="../5.RRefMovSemPerfForw/item25.html">Item 25:对于右值引用使用std::move对于通用引用使用std::forward</a></li><li class="chapter-item expanded "><a href="../5.RRefMovSemPerfForw/item26.html">Item 26:避免重载通用引用</a></li><li class="chapter-item expanded "><a href="../5.RRefMovSemPerfForw/item27.html">Item 27:熟悉重载通用引用的替代品</a></li><li class="chapter-item expanded "><a href="../5.RRefMovSemPerfForw/item28.html">Item 28:理解引用折叠</a></li><li class="chapter-item expanded "><a href="../5.RRefMovSemPerfForw/item29.html">Item 29:认识移动操作的缺点</a></li><li class="chapter-item expanded "><a href="../5.RRefMovSemPerfForw/item30.html">Item 30:熟悉完美转发失败的情况</a></li></ol></li><li class="chapter-item expanded "><div>第六章 Lambda表达式</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="../6.LambdaExpressions/item31.html">Item 31:避免使用默认捕获模式</a></li><li class="chapter-item expanded "><a href="../6.LambdaExpressions/item32.html">Item 32:使用初始化捕获来移动对象到闭包中</a></li><li class="chapter-item expanded "><a href="../6.LambdaExpressions/item33.html">Item 33:对于std::forward的auto&&形参使用decltype</a></li><li class="chapter-item expanded "><a href="../6.LambdaExpressions/item34.html">Item 34:优先考虑lambda表达式而非std::bind</a></li></ol></li><li class="chapter-item expanded "><div>第七章 并发API</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="../7.TheConcurrencyAPI/Item35.html">Item 35:优先考虑基于任务的编程而非基于线程的编程</a></li><li class="chapter-item expanded "><a href="../7.TheConcurrencyAPI/item36.html">Item 36:如果有异步的必要请指定std::launch::threads</a></li><li class="chapter-item expanded "><a href="../7.TheConcurrencyAPI/item37.html">Item 37:从各个方面使得std::threads unjoinable</a></li><li class="chapter-item expanded "><a href="../7.TheConcurrencyAPI/item38.html">Item 38:关注不同线程句柄析构行为</a></li><li class="chapter-item expanded "><a href="../7.TheConcurrencyAPI/item39.html">Item 39:考虑对于单次事件通信使用void</a></li><li class="chapter-item expanded "><a href="../7.TheConcurrencyAPI/item40.html">Item 40:对于并发使用std::atomicvolatile用于特殊内存区</a></li></ol></li><li class="chapter-item expanded "><div>第八章 微调</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="../8.Tweaks/item41.html">Item 41:对于那些可移动总是被拷贝的形参使用传值方式</a></li><li class="chapter-item expanded "><a href="../8.Tweaks/item42.html">Item 42:考虑就地创建而非插入</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">Effective Modern C++</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/CnTransGroup/EffectiveModernCppChinese" title="Git repository" aria-label="Git repository">
<i id="git-repository-button" class="fa fa-github"></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">
<main>
<h2 id="条款八优先考虑nullptr而非0和null"><a class="header" href="#条款八优先考虑nullptr而非0和null">条款八:优先考虑<code>nullptr</code>而非<code>0</code><code>NULL</code></a></h2>
<p><strong>Item 8: Prefer <code>nullptr</code> to <code>0</code> and <code>NULL</code></strong></p>
<p>你看这样对不对:字面值<code>0</code>是一个<code>int</code>不是指针。如果C++发现在当前上下文只能使用指针,它会很不情愿的把<code>0</code>解释为指针但是那是最后的退路。一般来说C++的解析策略是把<code>0</code>看做<code>int</code>而不是指针。</p>
<p>实际上,<code>NULL</code>也是这样的。但在<code>NULL</code>的实现细节有些不确定因素,因为实现被允许给<code>NULL</code>一个除了<code>int</code>之外的整型类型(比如<code>long</code>)。这不常见,但也算不上问题所在。这里的问题不是<code>NULL</code>没有一个确定的类型,而是<code>0</code><code>NULL</code>都不是指针类型。</p>
<p>在C++98中对指针类型和整型进行重载意味着可能导致奇怪的事情。如果给下面的重载函数传递<code>0</code><code>NULL</code>,它们绝不会调用指针版本的重载函数:</p>
<pre><code class="language-cpp">void f(int); //三个f的重载函数
void f(bool);
void f(void*);
f(0); //调用f(int)而不是f(void*)
f(NULL); //可能不会被编译一般来说调用f(int)
//绝对不会调用f(void*)
</code></pre>
<p><code>f(NULL)</code>的不确定行为是由<code>NULL</code>的实现不同造成的。如果<code>NULL</code>被定义为<code>0L</code>(指的是<code>0</code><code>long</code>类型),这个调用就具有二义性,因为从<code>long</code><code>int</code>的转换或从<code>long</code><code>bool</code>的转换或<code>0L</code><code>void*</code>的转换都同样好。有趣的是源代码<strong>表现出</strong>的意思(“我使用空指针<code>NULL</code>调用<code>f</code>”)和<strong>实际表达出</strong>的意思(“我是用整型数据而不是空指针调用<code>f</code>是相矛盾的。这种违反直觉的行为导致C++98程序员都将避开同时重载指针和整型作为编程准则译注请务必注意结合上下文使用这条规则。在C++11中这个编程准则也有效因为尽管我这个条款建议使用<code>nullptr</code>,可能很多程序员还是会继续使用<code>0</code><code>NULL</code>,哪怕<code>nullptr</code>是更好的选择。</p>
<p><code>nullptr</code>的优点是它不是整型。老实说它也不是一个指针类型,但是你可以把它认为是<strong>所有</strong>类型的指针。<code>nullptr</code>的真正类型是<code>std::nullptr_t</code>,在一个完美的循环定义以后,<code>std::nullptr_t</code>又被定义为<code>nullptr</code><code>std::nullptr_t</code>可以隐式转换为指向任何内置类型的指针,这也是为什么<code>nullptr</code>表现得像所有类型的指针。</p>
<p>使用<code>nullptr</code>调用<code>f</code>将会调用<code>void*</code>版本的重载函数,因为<code>nullptr</code>不能被视作任何整型:</p>
<pre><code class="language-cpp">f(nullptr); //调用重载函数f的f(void*)版本
</code></pre>
<p>使用<code>nullptr</code>代替<code>0</code><code>NULL</code>可以避开了那些令人奇怪的函数重载决议,这不是它的唯一优势。它也可以使代码表意明确,尤其是当涉及到与<code>auto</code>声明的变量一起使用时。举个例子,假如你在一个代码库中遇到了这样的代码:</p>
<pre><code class="language-cpp">auto result = findRecord( /* arguments */ );
if (result == 0) {
}
</code></pre>
<p>如果你不知道<code>findRecord</code>返回了什么(或者不能轻易的找出),那么你就不太清楚到底<code>result</code>是一个指针类型还是一个整型。毕竟,<code>0</code>(用来测试<code>result</code>的值的那个)也可以像我们之前讨论的那样被解析。但是换一种假设如果你看到这样的代码:</p>
<pre><code class="language-cpp">auto result = findRecord( /* arguments */ );
if (result == nullptr) {
}
</code></pre>
<p>这就没有任何歧义:<code>result</code>的结果一定是指针类型。</p>
<p>当模板出现时<code>nullptr</code>就更有用了。假如你有一些函数只能被合适的已锁互斥量调用。每个函数都有一个不同类型的指针:</p>
<pre><code class="language-cpp">int f1(std::shared_ptr&lt;Widget&gt; spw); //只能被合适的
double f2(std::unique_ptr&lt;Widget&gt; upw); //已锁互斥量
bool f3(Widget* pw); //调用
</code></pre>
<p>如果这样传递空指针:</p>
<pre><code class="language-cpp">std::mutex f1m, f2m, f3m; //用于f1f2f3函数的互斥量
using MuxGuard = //C++11的typedef参见Item9
std::lock_guard&lt;std::mutex&gt;;
{
MuxGuard g(f1m); //为f1m上锁
auto result = f1(0); //向f1传递0作为空指针
} //解锁
{
MuxGuard g(f2m); //为f2m上锁
auto result = f2(NULL); //向f2传递NULL作为空指针
} //解锁
{
MuxGuard g(f3m); //为f3m上锁
auto result = f3(nullptr); //向f3传递nullptr作为空指针
} //解锁
</code></pre>
<p>令人遗憾前两个调用没有使用<code>nullptr</code>,但是代码可以正常运行,这也许对一些东西有用。但是重复的调用代码——为互斥量上锁,调用函数,解锁互斥量——更令人遗憾。它让人很烦。模板就是被设计于减少重复代码,所以让我们模板化这个调用流程:</p>
<pre><code class="language-cpp">template&lt;typename FuncType,
typename MuxType,
typename PtrType&gt;
auto lockAndCall(FuncType func,
MuxType&amp; mutex,
PtrType ptr) -&gt; decltype(func(ptr))
{
MuxGuard g(mutex);
return func(ptr);
}
</code></pre>
<p>如果你对函数返回类型(<code>auto ... -&gt; decltype(func(ptr))</code>)感到困惑不解,<a href="../1.DeducingTypes/item3.html">Item3</a>可以帮助你。在C++14中代码的返回类型还可以被简化为<code>decltype(auto)</code></p>
<pre><code class="language-cpp">template&lt;typename FuncType,
typename MuxType,
typename PtrType&gt;
decltype(auto) lockAndCall(FuncType func, //C++14
MuxType&amp; mutex,
PtrType ptr)
{
MuxGuard g(mutex);
return func(ptr);
}
</code></pre>
<p>可以写这样的代码调用<code>lockAndCall</code>模板(两个版本都可):</p>
<pre><code class="language-cpp">auto result1 = lockAndCall(f1, f1m, 0); //错误!
...
auto result2 = lockAndCall(f2, f2m, NULL); //错误!
...
auto result3 = lockAndCall(f3, f3m, nullptr); //没问题
</code></pre>
<p>代码虽然可以这样写,但是就像注释中说的,前两个情况不能通过编译。在第一个调用中存在的问题是当<code>0</code>被传递给<code>lockAndCall</code>模板,模板类型推导会尝试去推导实参类型,<code>0</code>的类型总是<code>int</code>,所以这就是这次调用<code>lockAndCall</code>实例化出的<code>ptr</code>的类型。不幸的是,这意味着<code>lockAndCall</code><code>func</code>会被<code>int</code>类型的实参调用,这与<code>f1</code>期待的<code>std::shared_ptr&lt;Widget&gt;</code>形参不符。传递<code>0</code><code>lockAndCall</code>本来想表示空指针,但是实际上得到的一个普通的<code>int</code>。把<code>int</code>类型看做<code>std::shared_ptr&lt;Widget&gt;</code>类型给<code>f1</code>自然是一个类型错误。在模板<code>lockAndCall</code>中使用<code>0</code>之所以失败是因为在模板中,传给的是<code>int</code>但实际上函数期待的是一个<code>std::shared_ptr&lt;Widget&gt;</code></p>
<p>第二个使用<code>NULL</code>调用的分析也是一样的。当<code>NULL</code>被传递给<code>lockAndCall</code>,形参<code>ptr</code>被推导为整型(译注:由于依赖于具体实现所以不一定是整数类型,所以用整型泛指<code>int</code><code>long</code>等类型),然后当<code>ptr</code>——一个<code>int</code>或者类似<code>int</code>的类型——传递给<code>f2</code>的时候就会出现类型错误,<code>f2</code>期待的是<code>std::unique_ptr&lt;Widget&gt;</code></p>
<p>然而,使用<code>nullptr</code>是调用没什么问题。当<code>nullptr</code>传给<code>lockAndCall</code>时,<code>ptr</code>被推导为<code>std::nullptr_t</code>。当<code>ptr</code>被传递给<code>f3</code>的时候,隐式转换使<code>std::nullptr_t</code>转换为<code>Widget*</code>,因为<code>std::nullptr_t</code>可以隐式转换为任何指针类型。</p>
<p>模板类型推导将<code>0</code><code>NULL</code>推导为一个错误的类型(即它们的实际类型,而不是作为空指针的隐含意义),这就导致在当你想要一个空指针时,它们的替代品<code>nullptr</code>很吸引人。使用<code>nullptr</code>,模板不会有什么特殊的转换。另外,使用<code>nullptr</code>不会让你受到同重载决议特殊对待<code>0</code><code>NULL</code>一样的待遇。当你想用一个空指针,使用<code>nullptr</code>,不用<code>0</code>或者<code>NULL</code></p>
<p><strong>记住</strong></p>
<ul>
<li>优先考虑<code>nullptr</code>而非<code>0</code><code>NULL</code></li>
<li>避免重载指针和整型</li>
</ul>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../3.MovingToModernCpp/item7.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="../3.MovingToModernCpp/item9.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="../3.MovingToModernCpp/item7.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="../3.MovingToModernCpp/item9.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>
<!-- Custom JS scripts -->
</body>
</html>