@@ -389,7 +389,7 @@ <h2 id="index-_1">前言</h2>
389
389
<blockquote>
390
390
<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>
391
391
</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>
393
393
<p><a href="https://parallel101.github.io/cppguidebook">在 GitHub Pages 浏览本书</a> | <a href="https://142857.red/book">在小彭老师自己维护的镜像上浏览本书</a></p>
394
394
<h2 id="index-_2">格式约定</h2>
395
395
<blockquote>
@@ -1053,7 +1053,6 @@ <h2 id="symbols-linkage">符号的链接类型 (linkage)</h2>
1053
1053
<li><a href="#cpp_tricks-_1">交换两个变量</a></li>
1054
1054
<li><a href="#cpp_tricks-raii">RAII 地分配一段内存空间</a></li>
1055
1055
<li><a href="#cpp_tricks-_2">读取整个文件到字符串</a></li>
1056
- <li><a href="#cpp_tricks-bit-field">位域(bit-field)</a></li>
1057
1056
<li><a href="#cpp_tricks-_3">别再写构造函数啦!</a></li>
1058
1057
<li><a href="#cpp_tricks-_4">别再写拷贝构造函数啦!</a></li>
1059
1058
<li><a href="#cpp_tricks-_5">提前返回</a></li>
@@ -1095,11 +1094,13 @@ <h2 id="symbols-linkage">符号的链接类型 (linkage)</h2>
1095
1094
<li><a href="#cpp_tricks-_12">函数默认参数求值的位置是调用者</a></li>
1096
1095
<li><a href="#cpp_tricks-_13">花括号实现安全的类型转换检查</a></li>
1097
1096
<li><a href="#cpp_tricks-_14">临时右值转左值</a></li>
1097
+ <li><a href="#cpp_tricks-ostringstream">ostringstream 格式化字符串</a></li>
1098
1098
<li><a href="#cpp_tricks-adl">ADL 机制</a></li>
1099
1099
<li><a href="#cpp_tricks-shared_from_this">shared_from_this</a></li>
1100
1100
<li><a href="#cpp_tricks-requires">requires 语法检测是否存在指定成员函数</a></li>
1101
1101
<li><a href="#cpp_tricks-locale-utf8_1">设置 locale 为 .utf8 解决编码问题</a></li>
1102
1102
<li><a href="#cpp_tricks-this_1">成员函数针对 this 的移动重载</a></li>
1103
+ <li><a href="#cpp_tricks-bit-field">位域(bit-field)</a></li>
1103
1104
</ul>
1104
1105
</li>
1105
1106
</ul>
@@ -1160,11 +1161,22 @@ <h2 id="cpp_tricks-raii">RAII 地分配一段内存空间</h2>
1160
1161
}
1161
1162
</code></pre>
1162
1163
<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
+ }
1167
1175
</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>
1168
1180
<h2 id="cpp_tricks-_3">别再写构造函数啦!</h2>
1169
1181
<pre><code class="language-cpp">// C++98
1170
1182
struct Student {
@@ -2378,11 +2390,51 @@ <h2 id="cpp_tricks-_14">临时右值转左值</h2>
2378
2390
<p><img src="../img/book.png" height="30px" width="auto" style="margin: 0; border: none"/> 在 Libreoffice 源码中就有应用这个帮手函数。</p>
2379
2391
<p><img src="../img/warning.png" height="30px" width="auto" style="margin: 0; border: none"/> 临时变量的生命周期是一行</p>
2380
2392
</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>
2381
2412
<h2 id="cpp_tricks-adl">ADL 机制</h2>
2413
+ <p>TODO</p>
2382
2414
<h2 id="cpp_tricks-shared_from_this">shared_from_this</h2>
2383
2415
<h2 id="cpp_tricks-requires">requires 语法检测是否存在指定成员函数</h2>
2384
2416
<h2 id="cpp_tricks-locale-utf8_1">设置 locale 为 .utf8 解决编码问题</h2>
2385
2417
<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>
2386
2438
<!-- ## vector + unordered_map = LRU cache -->
2387
2439
<!-- -->
2388
2440
<!-- ## Lambda 捕获 unique_ptr 导致 function 报错怎么办 -->
@@ -3304,6 +3356,22 @@ <h2 id="lambda-bind">bind 为函数对象绑定参数</h2>
3304
3356
<blockquote>
3305
3357
<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>
3306
3358
</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>
3307
3375
<h3 id="lambda-bind_1">bind 是一个失败的设计</h3>
3308
3376
<p>当我们绑定出来的函数对象还需要接受参数时,就变得尤为复杂:需要使用占位符(placeholder)。</p>
3309
3377
<pre><code class="language-cpp">int func(int x, int y, int z, int &w);
0 commit comments