@@ -389,7 +389,7 @@ <h2 id="index-_1">前言</h2>
389389<blockquote>
390390<p><img src="../img/bulb.png" height="30px" width="auto" style="margin: 0; border: none"/> 本书还在持续更新中……要追番的话,可以在 <a href="https://github.com/parallel101/cppguidebook">GitHub</a> 点一下右上角的 “Watch” 按钮,每当小彭老师提交新 commit,GitHub 会向你发送一封电子邮件,提醒你小彭老师更新了。</p>
391391</blockquote>
392- <p>更新时间:2024年08月28日 22:50:53 (UTC+08:00)</p>
392+ <p>更新时间:2024年08月28日 23:08:04 (UTC+08:00)</p>
393393<p><a href="https://parallel101.github.io/cppguidebook">在 GitHub Pages 浏览本书</a> | <a href="https://142857.red/book">在小彭老师自己维护的镜像上浏览本书</a></p>
394394<h2 id="index-_2">格式约定</h2>
395395<blockquote>
@@ -1053,7 +1053,6 @@ <h2 id="symbols-linkage">符号的链接类型 (linkage)</h2>
10531053<li><a href="#cpp_tricks-_1">交换两个变量</a></li>
10541054<li><a href="#cpp_tricks-raii">RAII 地分配一段内存空间</a></li>
10551055<li><a href="#cpp_tricks-_2">读取整个文件到字符串</a></li>
1056- <li><a href="#cpp_tricks-bit-field">位域(bit-field)</a></li>
10571056<li><a href="#cpp_tricks-_3">别再写构造函数啦!</a></li>
10581057<li><a href="#cpp_tricks-_4">别再写拷贝构造函数啦!</a></li>
10591058<li><a href="#cpp_tricks-_5">提前返回</a></li>
@@ -1095,11 +1094,13 @@ <h2 id="symbols-linkage">符号的链接类型 (linkage)</h2>
10951094<li><a href="#cpp_tricks-_12">函数默认参数求值的位置是调用者</a></li>
10961095<li><a href="#cpp_tricks-_13">花括号实现安全的类型转换检查</a></li>
10971096<li><a href="#cpp_tricks-_14">临时右值转左值</a></li>
1097+ <li><a href="#cpp_tricks-ostringstream">ostringstream 格式化字符串</a></li>
10981098<li><a href="#cpp_tricks-adl">ADL 机制</a></li>
10991099<li><a href="#cpp_tricks-shared_from_this">shared_from_this</a></li>
11001100<li><a href="#cpp_tricks-requires">requires 语法检测是否存在指定成员函数</a></li>
11011101<li><a href="#cpp_tricks-locale-utf8_1">设置 locale 为 .utf8 解决编码问题</a></li>
11021102<li><a href="#cpp_tricks-this_1">成员函数针对 this 的移动重载</a></li>
1103+ <li><a href="#cpp_tricks-bit-field">位域(bit-field)</a></li>
11031104</ul>
11041105</li>
11051106</ul>
@@ -1160,11 +1161,22 @@ <h2 id="cpp_tricks-raii">RAII 地分配一段内存空间</h2>
11601161}
11611162</code></pre>
11621163<h2 id="cpp_tricks-_2">读取整个文件到字符串</h2>
1163- <pre><code class="language-cpp">TODO
1164- </code></pre>
1165- <h2 id="cpp_tricks-bit-field">位域(bit-field)</h2>
1166- <pre><code class="language-cpp">TODO
1164+ <pre><code class="language-cpp">std::string file_get_content(std::string const &filename) {
1165+ std::ifstream ifs(filename, std::ios::in | std::ios::binary);
1166+ std::istreambuf_iterator<char> iit(ifs), iite;
1167+ std::string content(iit, iite);
1168+ return content;
1169+ }
1170+
1171+ void file_put_content(std::string const &filename, std::string const &content) {
1172+ std::ofstream ofs(filename, std::ios::out | std::ios::binary);
1173+ ofs << content;
1174+ }
11671175</code></pre>
1176+ <p>这样就可以把整个文件读取到内存中,进行处理后再写回文件。</p>
1177+ <blockquote>
1178+ <p><img src="../img/question.png" height="30px" width="auto" style="margin: 0; border: none"/> 用 <code>std::ios::binary</code> 选项打开文件,是为了避免文件中出现 <code>'\n'</code> 时,被 MSVC 标准库自动转换成 <code>'\r\n'</code>,以保证跨平台。</p>
1179+ </blockquote>
11681180<h2 id="cpp_tricks-_3">别再写构造函数啦!</h2>
11691181<pre><code class="language-cpp">// C++98
11701182struct Student {
@@ -2378,11 +2390,51 @@ <h2 id="cpp_tricks-_14">临时右值转左值</h2>
23782390<p><img src="../img/book.png" height="30px" width="auto" style="margin: 0; border: none"/> 在 Libreoffice 源码中就有应用这个帮手函数。</p>
23792391<p><img src="../img/warning.png" height="30px" width="auto" style="margin: 0; border: none"/> 临时变量的生命周期是一行</p>
23802392</blockquote>
2393+ <h2 id="cpp_tricks-ostringstream">ostringstream 格式化字符串</h2>
2394+ <pre><code class="language-cpp">std::string name = "你好";
2395+ int answer = 42;
2396+ auto str = std::format("你好,{}!答案是 {},十六进制:0x{:02x}\n", name, answer, answer);
2397+ </code></pre>
2398+ <p>没有 C++20 之前,要么使用第三方的 <code>fmt::format</code>,要么只能使用字符串的 <code>+</code> 运算符拙劣地拼接:</p>
2399+ <pre><code class="language-cpp">auto str = std::string("你好,") + name + "!答案是 " + std::to_string(answer) + ",十六进制:0x" + std::to_string(answer) + "\n";
2400+ </code></pre>
2401+ <p>这样做效率低下,且不易阅读。而且也无法实现数字按“十六进制”转字符串。</p>
2402+ <p>可以用 <code>std::ostringstream</code>,其用法与 <code>std::cout</code> 相同。只不过会把结果写入一个字符串(而不是直接输出),可以用 <code>.str()</code> 取出那个字符串。</p>
2403+ <pre><code class="language-cpp">#include <sstream>
2404+
2405+ std::ostringstream oss;
2406+ oss << "你好," << name << "!答案是 " << answer << ",十六进制:0x" << std::hex << std::setfill('0') << std::setw(2) << answer << "\n";
2407+ auto str = oss.str();
2408+ </code></pre>
2409+ <p>利用临时变量语法,可以浓缩写在一行里,做个 format 拙劣的模仿者:</p>
2410+ <pre><code class="language-cpp">auto str = (std::ostringstream() << "你好," << name << "!答案是 " << answer << ",十六进制:0x" << std::hex << std::setfill('0') << std::setw(2) << answer << "\n").str();
2411+ </code></pre>
23812412<h2 id="cpp_tricks-adl">ADL 机制</h2>
2413+ <p>TODO</p>
23822414<h2 id="cpp_tricks-shared_from_this">shared_from_this</h2>
23832415<h2 id="cpp_tricks-requires">requires 语法检测是否存在指定成员函数</h2>
23842416<h2 id="cpp_tricks-locale-utf8_1">设置 locale 为 .utf8 解决编码问题</h2>
23852417<h2 id="cpp_tricks-this_1">成员函数针对 this 的移动重载</h2>
2418+ <h2 id="cpp_tricks-bit-field">位域(bit-field)</h2>
2419+ <p>在互联网编程和各种与硬盘、序列化打交道的场景中,常常需要按位拆分单个字节。</p>
2420+ <p>C 语言有专门照顾此类工作的语法糖:位域。</p>
2421+ <p>位域是一种特殊的结构体成员,可以对位进行分组,方便读取。例如,我们想要从一个字节中读取三个状态位:</p>
2422+ <pre><code class="language-cpp">struct Flag {
2423+ uint8_t a : 4; // 低 4 位
2424+ uint8_t b : 4; // 高 4 位
2425+ };
2426+
2427+ sizeof(Flag); // 1 字节大小(共 8 位)
2428+
2429+ Flag f = std::bit_cast<Flag>(0x21);
2430+ f.a; // 0x1
2431+ f.b; // 0x2
2432+ </code></pre>
2433+ <p>以上的代码等价于:</p>
2434+ <pre><code class="language-cpp">uint8_t f = 0x21;
2435+ int a = f & 0xF; // 0x1
2436+ int b = f >> 4; // 0x2
2437+ </code></pre>
23862438<!-- ## vector + unordered_map = LRU cache -->
23872439<!-- -->
23882440<!-- ## Lambda 捕获 unique_ptr 导致 function 报错怎么办 -->
@@ -3304,6 +3356,22 @@ <h2 id="lambda-bind">bind 为函数对象绑定参数</h2>
33043356<blockquote>
33053357<p><img src="../img/warning.png" height="30px" width="auto" style="margin: 0; border: none"/> 如果不使用 <code>std::ref</code>,那么 <code>main</code> 里的局部变量 <code>x</code> 不会改变!因为 <code>std::bind</code> 有一个恼人的设计:默认按拷贝捕获,会把参数拷贝一份,而不是保留引用。</p>
33063358</blockquote>
3359+ <p>有趣的是,placeholder 指定的参数,却不需要 <code>std::ref</code> 才能保持引用:</p>
3360+ <pre><code class="language-cpp">int inc(int &x, int y) {
3361+ x += y;
3362+ }
3363+
3364+ int main() {
3365+ int x = 0;
3366+ auto inc1 = std::bind(inc, std::placeholders::_1, 1);
3367+ inc1(x); // 此处 x 是按引用传递的
3368+ fmt::println("x = {}", x); // x = 1
3369+ inc1(x);
3370+ fmt::println("x = {}", x); // x = 2
3371+ return 0;
3372+ }
3373+ </code></pre>
3374+ <p>那是因为,<code>std::placeholders::_1</code> 指定的参数会被直接完美转发给 <code>inc</code> 里的 <code>x</code>,相当于 <code>inc(x, 2);</code>。只有捕获的参数会发生拷贝,不会完美转发。</p>
33073375<h3 id="lambda-bind_1">bind 是一个失败的设计</h3>
33083376<p>当我们绑定出来的函数对象还需要接受参数时,就变得尤为复杂:需要使用占位符(placeholder)。</p>
33093377<pre><code class="language-cpp">int func(int x, int y, int z, int &w);
0 commit comments