罗剑锋的C++实战笔记-学习笔记(2)
书接上文,三句名言镇楼。
三句名言镇楼
-
任何人都能写出机器能看懂的代码,只有优秀的程序员才能写出人看懂的代码
-
两种写程序的方式:把代码写的非常复杂,以至于"看不出明显错误"。把代码写的非常简单,以至于"明显看不出错误"。
-
把正确的代码改快速,要比把快速的代码改正确,容易得太多。
处理错误方案
-
错误码:检查函数执行返回值或者
errno
码,依此为下一步行动依据。
缺点:- 将业务代码和错误处理代码混在一起。
- 错误码可以被忽略,可能会引发更多错误
-
异常
- 通过
throw
抛出异常,在catch
中统一处理异常,分离业务代码和错误处理代码。 - 不能被忽略
- 适用于无法使用错误码的场合
- 通过
有一种包裹整个函数体的异常写法:
void some_fun()
try
{
}
catch(...)
{
}
上述写法可以捕获some_fun
执行过程中所有可能的异常,而且少一级缩进,处理逻辑更清晰。
一般认为,普通构造、拷贝构造、转移构造、析构函数应尽量声明为noexcept
,告知编译器据此进行优化。析构函数保证绝对不会抛异常。
Lambda
Lambda
函数内部的捕获变量,需要在函数体声明前方声明,才能正确捕获。
auto f2 = [&]()
{
x_ += 10; // 在函数体之前,是看不到x_变量的,编译出错。
};
int x_ = 33;
一句话,按需捕获,最小化对外部的影响。每个 lambda
表达式的类型都是唯一的,可使用 std::function
类来存储。
字符串和容器的区别
字符串是文本,内部的字符之间是强关系,顺序不能随便调换,否则就失去了本意,通常视为一个整体来处理。
容器是容纳某种类型数据的集合,内部元素之间没有任何关系,对容器来说,可以随意增删改,以单个元素为操作单位。
使用R
来表示原始字符串,比如
auto str1 = R"(\r\n\t)"; // 表示 str1 为 \r\n\t 这6个字符。
STL中的算法
相比于手写for
循环,STL
中的内置算法是更高层次的抽象和封装。 关注做了什么,而不关心是怎么做的。
比如说,统计一个序列中大于2的元素个数。
#include <algorithm>
auto n = std::count_if(begin(v), end(v), [](auto& v){
return v > 2;
});
算法通过迭代器间接访问容器内部元素,分离了数据和对数据的操作,使得算法适用范围更广,更灵活。为了兼顾效率,在通用算法之上,STL
针对merge
、sort
、unique
等算法,提供了各容器的特化版本,针对数据结构特点进行针对性的优化。例如list
容器有sort
成员函数。
打印数组中的元素,可使用如下套路:
auto print = [](const auto& x) {
cout << x << ",";
};
std::sort(begin(arr), end(arr)); // 先排序
for_each(cbegin(arr), cend(arr), print); // 再输出
for_each
将元素遍历和对每个元素的操作分开,含义明确,达到更好的封装。
排序算法
- 要求稳定,使用
stable_sort
- 选出前几名(TopN),并要求有序,使用
partial_sort
- 选出前几名(BestN),不要求有序,使用
nth_element
。 该算法适用于中位数、百分位数 - 分成两组,使用
partition
- 求最大、最小值,使用
minmax_element
多线程开发实践
- 仅调用一次:
std::call_once
函数,在多线程调用时,保证可调用对象只会调用一次。 - 线程局部存储:
thread_local
关键字。 - 原子变量:原子变量禁用拷贝构造函数,不能使用
=
赋值,只能用圆括号或花括号。 - 将线程往上抽象一层,运行一个异步任务,可使用
std::async
#include <future>
#include <thread>
using std::this_thread::sleep_for;
#include <chrono>
void test_async()
{
auto task = [](auto x) {
cout << "begin sleep for" << x << endl;
sleep_for(std::chrono::milliseconds(x * 1000));
cout << "end sleep for" << x << endl;
return x;
};
auto f = std::async(task, 50); // 启动一个异步任务
f.wait(); // 等待任务完成
assert(f.valid()); // 确实已经完成任务
cout << f.get() << endl; // 获得任务执行结果(只能调用一次)
}
应用层常用库
数据交换格式
- json: 纯文本,容易阅读,编辑,适用性最广。可使用
nlohmann/json.hpp
来解析 MessagePack
: 二进制格式,小巧高效,但只对基本类型和标准容器进行序列化/反序列化。ProtoBuffer
:Google
出品的工业级数据格式,注重安全和性能。
网络通讯
libcurl
: 著名开源curl
项目的底层核心库,C语言编写,兼容性强。cpr
是libcurl
的c++11
封装版本,常用于客户端程序。ZMQ
: 网络通讯库,支持多种通讯模式,可把消息队列直接嵌入应用程序。