概述
阅读时请配合cpp reference使用,同时请确保编译器支持C++20。在bilibili上有对应的讲解,这位up主更是为cpp高手,以下大部分内容均是这位up主的笔记,为了方便速查选择我常用的部分搬运了过来。
第一章 C++20的新特性
1.2格式化文本
#include<iostream>
#include<algorithm>
#include<string_view>
#include<format>
template < typename... Args>
void print(const std::string_view fmt_str, Args&&... args) {
auto fmt_args{ std::make_format_args(args...) };
std::string outstr{ std::vformat(fmt_str, fmt_args) };
fputs(outstr.c_str(), stdout);
}
struct Frac {
int a, b;
};
template<>
struct std::formatter<Frac> {
template<typename ParseContext>
constexpr auto parse(ParseContext& ctx) {
return ctx.begin();
}
template<typename FormatContext>
auto format(const Frac& f, FormatContext& ctx)const {
return std::format_to(ctx.out(), "{0:d}/{1:d}", f.a, f.b);
}
};
int main() {
Frac f{ 1,10 };
print("{}", f);
}
//特化规则参见: https://zh.cppreference.com/w/cpp/named_req/Formatter
out: 1/10
- 实现模板函数
print
使用与std::format()
函数相同的参数。第一个参数是格式字符串的 std::string_view
对象,后面作为参数 的可变参数包。
std::make_format_args()
函数的作用: 接受参数包并返回一个对象,该对象包含适合格式化的已擦除 类型的值。
fmt_str
就是传递的格式化字符串,fmt_args
是一个保有格式化参数的对象,使用std::vformat(fmt_str, fmt_args)
即可返回格式化完毕的字符串。我们使用 fputs()
将值输出到控制台上 (这比 cout
高效得多)。
对于自定义,或者说标准没有对其有特化的类型,需要我们自行特化std::formatter
才可以正确的格式化。
parse()
函数解析格式字符串,从冒号之后 (若没有冒号,则在开大括号之后) 直到但不包括结 束大括号 (就是指定对象类型的部分)。其接受一个 ParseContext
对象,并返回一个迭代器。这里,可以只返回 begin()
迭代器。因为我们的类型不需要新语法,所以无需准备任何东西。
format()
函数接受一个 Frac
对象和一个 FormatContext
对象,返回结束迭代器。format_to()
函数可使这变得很容易,其可以接受一个迭代器、一个格式字符串和一个参数包。本例中,参数包是 Frac 类的两个属性,分子和分母。 需要做的就是提供一个简单的格式字符串“{0}/{1}”
以及分子和分母的值 (0 和 1 表示参数的 位置)。
1.3使用编译时constexpr std::vector
和std::string
#include<iostream>
#include<vector>
constexpr auto f() {
std::vector<int>v{ 1,2,3 };
return v;
}
constexpr auto f2() {
int* p = new int{ 10 };
//未delete解除分配
return *p;
}
int main() {
constexpr auto n = f().size();//√
//constexpr auto n2 = f()//error
//constexpr auto n3 = f2()//error
}
C++20
允许在新的上下文中使用 constexpr
,这些语句可以在编译时计算,其中包括在 constexpr
上下文中使用 string
和 vector
对象的能力。所以 ,这些对象本身可能不声 明为 constexpr,但可以在编译时上下文中使用,同时也可以使用STL中的算法。C++20
开始,标准 string
和 vector
类具有constexpr
限定的构造函数和析构函数,这是可在编译时使用的 前提。所以,分配给 string
或 vector
对象的内存,也必须在编译时释放。注意该对象在运行时实际上是不可用的,就算能通过编译也无法使用。
1.4安全比较不同类型的整数cmp_less
#include<iostream>
template<class T,class U>
constexpr bool cmp_less(T t, U u)noexcept {
using UT = std::make_unsigned_t<T>;//有符号类型到无符号类型的安全转换。
using UU = std::make_unsigned_t<U>;
if constexpr (std::is_signed_v <T> == std::is_signed_v<U>)
return t < u;
else if constexpr (std::is_signed_v<T>)
return t < 0 ? true : UT(t) < u;
else
return u < 0 ? false : t < UU(u);
}
int main() {
std::cout << std::boolalpha << (5u < -1) << '\n';//true
std::cout << std::boolalpha << ::cmp_less(5u, 1) << '\n';//false
std::cout << std::boolalpha << ::cmp_less(5u, 2u) << '\n';//false
}
C++20
在 utility
引入了一组比较函数,他们分别是:
std::cmp_equal
std::cmp_not_equal
std::cmp_less
std::cmp_greater
std::cmp_less_equal
std::cmp_greater_equal
1.5三路比较运算符
三路比较运算符表达式的形式为表达式1 <=> 表达式2
该表达式将返回一个对象
- 若
表达式1 < 表达式2
,则(表达式1 <=> 表达式2) < 0
- 若
表达式1 > 表达式2
,则(表达式1 <=> 表达式2) > 0
- 若
表达式1 == 表达式2
,则(表达式1 <=> 表达式2) == 0
每当<
>
<=
>=
<=>
被比较且重载决议选择该重载时,operator<=>
都会被调用
1.7概念(concept
)和约束(constraint
)-创建更安全的模板
重点内容,这个功能可以说让模板焕发新生。
#include<iostream>
template<std::integral T>
void f(T t) {}
template<class T>
requires std::integral<T> || std::is_pointer_v<T>
struct X {};
template <class T>
requires std::is_integral_v<T>
T n{};
template <class T>
concept love = std::is_integral_v<T> && (std::is_same_v<int, T> || std::is_same_v<uint32_t, T>);
void f2(love auto){}
int main() {
f(1); // 1 是 int,约束满足
f('*'); // '*' 是整数类型(Integer Type)之一,约束满足
//f(1.2);
X<int> x; // int 满足两个约束的析取之一:std::integral<T>,约束满足
//X<double>x2;
X<double*> x3; // double* 满足两个约束的析取之一:std::is_pointer_t<T>,约束满足
n<int> = 3;
//n<double>;
std::cout << n<int> << '\n';
f2(1); // 满足合取 std::is_integral_v<T> 和 std::is_same_v<int, T>
f2(1u); // 满足合取 std::is_integral_v<T>,std::is_same_v<uint32_t, T>
//f2(1l);
}
//Requires表达式 https://zh.cppreference.com/w/cpp/language/requires
//约束与概念 https://zh.cppreference.com/w/cpp/language/constraints
作为 C++20
引入的四大新特性之一:Concept
,提出了一种比 SFINAE 更好的约束方法,它易于理解和编写,也能在出现问题时给出更可读的编译期报错。概念的定义形式如下:
template < 模板形参列表 > *concept *概念名 属性(可选) **= 约束表达式 ;
在上述例子中,概念 love
的定义就是这样:
template <class T>
concept love = std::is_integral_v<T> && (std::is_same_v<int, T> || std::is_same_v<uint32_t, T>);
requires
关键字可用于进行多个约束的分开表达,约束之间的关系均为合取,分为以下多种情况:
- 简单约束
// 1. 简单约束
template <typename T>
concept Addable = requires(T a, T b){
a + b; //编译器会检查该表达式是否 "合法"
}
- 类型约束
template <typename T>
struct tmp{
using value = T;
};
template <typename T, typename = std::enable_if_t<std::is_same_v<T, V>>
struct test {};
template <typename T>
using Ref = T&;
template <typename T>
concept Cpt = requires
{
typename T::value; // 检查 T 是否存在成员 T::value
typename X<T> // 检查是否存在模板类 S 的特化 S<T>
typename Ref<T> // 检查是否存在合法别名模板 Ref<T>
}
- 复合约束 复合约束用于约束表达式的返回类型。其定义为:
{ 表达式 } noexcept(可选) -> 类型约束 ;
例如:
template <typename T>
concept C = requires(T x) {
{x * 2} -> typename T::inner; // 表达式 x * 2 的类型可转换为 T::inner
{x + 3} -> std::same_as<int>; // 表达式 x + 3 需要满足约束 std::same_as<int>
};
1.8模块
1.8模块.cpp
import test;
int main() {
/*int n[]{
#include"t.txt"
};
for (auto i : n) {
std::cout << i << ' ';
}*/
std::cout << mylib::add(1, 2) << '\n';
//mylib::print("*");
t();
}
//模块: https://zh.cppreference.com/w/cpp/language/modules
//编译设置:add_executable (Test1 "src/1.8模块.cpp" "src/test.ixx" "src/test2.ixx")
module;
#define PI 3.14
export module test;
export import<iostream>;
export import test2;
namespace mylib {
export auto add(std::integral auto a, std::integral auto b) {
return a + b;
}
auto print(auto t) {
std::cout << t << '\n';
}
}
export module test2;
import<iostream>;
export void t() {
std::cout << "乐\n";
}
1,2,3,4,5
1.9视图
#include<iostream>
#include<ranges>
#include<vector>
namespace stdv = std::views;
namespace stdr = std::ranges;
void print(stdr::range auto v) {
for (const auto& i : v)std::cout << i << ' ';
endl(std::cout);
}
int main() {
std::vector nums{ 1,2,3,4,5,6,7,8,9,10 };
auto ret = nums | stdv::take(5) | stdv::reverse;
print(ret);
auto ret2 = nums | stdv::filter([](int i) {return i > 6; });
print(ret2);
auto ret3 = nums | stdv::transform([](int i) {return i * i; });
print(ret3);
print(nums);//视图是不会改变原来的数据的
std::vector<std::string>strs{ "🐴","🐭","🥵","🤣" };
auto ret4 = strs | stdv::reverse;
print(ret4);
auto ret5 = nums | stdv::filter([](int i) {return i % 2 != 0; }) | stdv::transform([](int i) {return i * 2; });
print(ret5);
auto nums_ = stdv::iota(1, 10);
print(nums_);
auto rnums = stdv::iota(1) | stdv::take(200);
print(rnums);
stdr::copy(strs | stdv::reverse | stdv::drop(2), std::ostream_iterator<std::string>(std::cout," "));
}
//范围库: https://zh.cppreference.com/w/cpp/ranges
第二章 STL的泛型特性
2.2span类
#include <iostream>
#include <format>
#include <span>
#include <vector>
#include <array>
template < typename... Args>
void print(const std::string_view fmt_str, Args&&... args) {
auto fmt_args{ std::make_format_args(args...) };
std::string outstr{ std::vformat(fmt_str, fmt_args) };
fputs(outstr.c_str(), stdout);
}
template<class T>
void pspan(std::span<T> s) {
print("number of elemnts: {}\n", s.size());// 返回序列中的元素个数
print("size of span: {}\n", s.size_bytes());// 返回以字节表示的序列大小
for (auto i : s) print("{} ", i);
endl(std::cout);
}
int main() {
int a[]{ 1, 2, 3, 4, 5, 6 };
pspan<int>(a);
std::endl(std::cout);
std::vector<int> b{1, 2, 3, 4, 5 };
pspan<int>(b);
std::endl(std::cout);
std::array<int, 4> c{ 1, 2, 3, 4 };
pspan<int>(c);
}
out:
number of elemnts: 6
size of span: 24
1 2 3 4 5 6
std::span
在C++20中被引入
它给具有连续对象的序列提供了轻量级的视图,以更加安全的方式对其进行迭代和索引,比如std::array
、 std::vector
、原生数组和原生指针。
常用于去包裹原生数组,并提供了更加安全的一系列函数:如front()
,begin()
, size()
, empty()
等
经典的实现中只有两个成员:
private:
pointer _ptr;//指向元素的指针
2.3结构化绑定
注意,由于结构化绑定使用自动类型推导,所以类型声明必须使用 auto
,且使用的变量名在该作用域内唯一,同时保证标识符列表内的标识符(即[a, b, c] 中的变量a,b,c)个数等于所指代对象的子元素个数。
Lambda表达式(C++11 起) 在C++17起才允许捕获结构化绑定的变量
struct S { int p{6}, q{7}; };
const auto& [b, d] = S{};
auto l = [b, d] { return b * d; }; // C++17 起合法
assert(l() == 42);
2.4if&switch中的初始化
void ifFunc(int n) {
if (auto flag = [n]() {return n; }(); flag % 2 == 0) {// C++17起,允许if语句内声明表达式,它可以是这里的lambda表达式
print("This is a even Number: {}\n", n);
}
}
void switchFunc() {
switch (char c = getchar(); c)// C++17起,允许switch语句内声明表达式,它可以是一条语句
{
case 'a':
print("a\n");
break;
default:
print("input not a b c\n");
break;
}
}
通过if & switcht 初始化语句
限制了变量的作用域,避免了与其他变量名发生冲突,并且会自动调用对应的析构函数,确保内存被安全释放。
2.5模板参数推导(CTAD)
#include"print.h"
using namespace std::string_literals;
template<class T>
struct X {
T v{};
template<class...Args>
X(Args&&...args) : v{ (args + ...) } {}
};
template<class...Ts>
X(Ts...ts) -> X<std::common_type_t<Ts...>>;//确定所有类型Ts...都能隐式转换到的类型
int main() {
X x("Hello ", "World🤣"s);
print("{}\n", x.v);
}
运行结果:
Hello World🤣
在C++17,当我们给定类模板实参时,编译器会对其进行自动类型推导,如上面代码代码中的实例化对象x
, 而之前为了实现`x对象的实例化,我们可能需要这样写:
X<const char*, std::string> x("Hello", "World"s);
虽然有了类模板实参推导
,但该类模板只接收一种类型,所以需要使用std::common_type_t
来对类模板实参进行一个都可隐式转换的类型的提取
因此,当我们初始化STL容器时,可以省略类型的书写:
std::pair p{ 2, 3.14 };// 省略容器元素的类型
std:vector vec{ 1, 2, 3, 4 };
std::sort(vec.begin(), vec.end(), std::greater<>());//省略比较器的类型
2.6编译期if
#include"print.h"
template<class T>
auto f(const T& v) {
if constexpr (std::is_pointer_v<T>)
print("is pointer\n");
else
print("not pointer\n");
}
template<class T,class...Args>
void show(T t, Args&&...args) {
print("{}\t",t);
if constexpr (sizeof...(args)) {
show(args...);
}
}
int main() {
int* p{};
f(p);
f(1);
show(5,314, "🤣", '*');
print("\n");
}
运行结果:
is pointer
not pointer
5 314 🤣 *
std::is_pointer
用于编译器判断参数类型T是否为对象/函数指针
以 if constexpr
开始的语句被称为 constexpr if 语句, 在 constexpr if 语句中, 若表达式的值可转换到bool类型的常量表达式,如果值为true
,舍弃false
分支(如果存在),反之亦然
被舍弃的分支中的return 语句
不参与函数的返回值类型推导,且可以使用未定义的变量(大概是因为他不会被执行到,所以无关紧要)
sizeof...`在编译期求出参数包的大小,值为0时,被决为`false
第三章 STL容器
3.3使用擦除函数从容器中擦除项
#include"print.h"
#include<vector>
#include<list>
template<typename Tc,typename Tv>
void remove_value(Tc& c, const Tv& v) {//C++20之前的做法
auto remove_it = std::remove(c.begin(), c.end(), v);//remove_it是首个需要被删除元素的位置
c.erase(remove_it, c.end());//删除remove_it到end()这个范围的元素
}
int main() {
std::vector v{ 1,2,3,4,5 };
print(v);
::remove_value(v, 1);
print(v);
std::erase(v,5);
print(v);
std::erase_if(v, [](int i) {return i % 2 != 0; });
print(v);
std::list list{ 1,2,3,4,5,6,7,8,9,10 };
std::erase(list, 5);
std::erase_if(list, [](int i) {return i % 2 == 0; });
print(list);
std::map<int, std::string> map{ {1,"🤣"},{2,"🥵"},{3,"🐴"},{4,"🐭"} };
print(map);
std::erase_if(map, [](auto& i) {
const auto& [k, v] = i;
return v == "🥵";
});
print(map);
}
运行结果
size: 5 [ 1 2 3 4 5 ]
size: 4 [ 2 3 4 5 ]
size: 3 [ 2 3 4 ]
size: 2 [ 2 4 ]
size: 4 [ 1 3 7 9 ]
size: 4 [ 1:🤣 2:🥵 3:🐴 4:🐭 ]
size: 3 [ 1:🤣 3:🐴 4:🐭 ]
解析
- 功能: 该函数用于将迭代器中与值匹配的元素移动到末尾,并返回操作完毕后首个与参数值匹配的元素位置
- 参数
_First
需要进行操作的容器的起始位置 - 参数
_Last
需要进行操作的容器的截止位置 - 参数
_Val
需要操作的值 - Ps:
std::remove
提供了可自定义操作规则的std::remove_if
- 功能: 删除给定容器中与
_Value
匹配的元素 - 参数
_Cont
需要被擦除元素的容器 - 参数
_Value
需要被擦除的值 - Ps: 该函数从
C++20
起,功能同 remove_value()
- 功能:
std::erase
的自定义删除规则版本 - 参数
_Cont
需要被擦除元素的容器 - 参数
_Pred
当该参数为true
时,擦除对应元素。该参数必须是一个可转换为bool
类型的表达式(此处使用一个lambda 表达式
来判断是否擦除) - Ps: 该函数是
std::erase
的改进版本,相较于旧版本只能单一匹配值来进行删除,std::erase_if
可以实现类似示例中的自定义删除规则
注意: std::erase
与 std::erase_if
会使序列容器迭代器失效
3.4常数时间内从未排序的向量中删除项
#include"print.h"
#include<vector>
#include<ranges>
namespace stdr = std::ranges;
//使用下标的版本
template<typename T>
void quick_delete(T& v, size_t idx) {
if (idx < v.size()) {
v[idx] = std::move(v.back());
v.pop_back();
}
}
//使用迭代器的版本
template<typename T>
void quick_delete(T& v, typename T::iterator it) {
if (it < v.end()) {
*it = std::move(v.back());
v.pop_back();
}
}
//若 vector 中项目的顺序不重要,就可以优化这个过程,使其花费 O(1)(常数) 时间
//做法很简单,将传入的要删除的迭代器或索引赋值为末尾元素的值,然后将末尾元素删除,就完成了,但是没有顺序
int main() {
std::vector v{ 1,2,3,4,5 };
print(v);
auto it = stdr::find(v, 3);
quick_delete(v, it);
print(v);//顺序不对,正常现象
quick_delete(v, 2);
print(v);
}
3.5安全的访问std::vector
元素
#include"print.h"
#include<vector>
void test1() {
std::vector v{ 1,2,3,4,5 };
v[5] = 2001;//写入非法内存,访问也是越界
auto& i = v[5];//引用了错误的内存
print("{}\n", i);//可能发生错误,不保证
}
void test2()try {
std::vector v{ 1,2,3,4,5 };
auto& i = v.at(5);// at会进行越界检查,保证了程序的安全
print("{}\n", i);
}
catch (std::exception& e) {
print("{}\n", e.what());
}
void test3()try {
std::vector v{ 1,2,3,4,5 };
auto& i = v[5];
print("{}\n", i);
}
catch (std::exception& e) {
print("{}\n", e.what());
}
int main() {
//test1();//error
test2();
//test3();//error
}
3.7高效的将元素插入到std::map
中
#include"print.h"
struct X {
std::string s;
X() { print("default construct\n"); }
X(const char* s) :s{ s } { print("construct\n"); }
X(const X&) { print("copy construct\n"); }
};
void printm(const std::map<int, X>& map) {
for (const auto& [k, v] : map) {
print("[ {}:{} ]", k, v.s);
}
print("\n");
}
int main() {
std::map<int, X>map{};
map[1] = "🐴";//两个构造的开销,有参和默认
print("\n");
//直接转发,只有一个有参构造的开销,这里使用try_emplace和emplace效果完全一样
map.emplace(2,"🥵");
map.emplace(3, "🤣");
printm(map);
print("\n");
map.emplace(1, "乐");//添加一个具有重复键的元素
map.try_emplace(1, "乐");
printm(map);
}
//重复键元素的问题参见 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=92300
3.8高效的修改std::map
项的键值
#include"print.h"
#include<string>
template<typename M,typename K>
bool node_swap(M& m, K k1, K k2) {
//extract 是更换 map 的键而不重分配的唯一方式
auto node1{ m.extract(k1) };
auto node2{ m.extract(k2) };
if (node1.empty() || node2.empty())
return false;
std::swap(node1.key(), node2.key());
m.insert(std::move(node1));
m.insert(std::move(node2));
return true;
}
int main() {
std::map<uint32_t, std::string>maps{
{1,"🐴"},{2,"🥵"},{3,"🤣"},{4,"🐭"},{5,"😘"}
};
print(maps);
::node_swap(maps, 3, 5);
print(maps);
auto node = maps.extract(maps.begin());
node.key() = 5;
auto t =maps.insert(std::move(node));
print(maps);
if (!t.inserted) {
print("插入失败 {}\n",t.position->second);
}
}
3.10使用set
进行输入和筛选
#include"print.h"
#include<set>
#include<string>
#include<ranges>
int main() {
std::set<std::string> sets;//set 容器用于存储键适用于索引关键字
std::copy(std::istream_iterator<std::string>{std::cin}, {},
std::inserter(sets, sets.end()));
print(sets);
}
std::copy
用于将数据拷贝到对应容器中
- 参数1
_First
需要拷贝的起始迭代器(这里使用istream
的迭代器来读取输入流字符串) - 参数2
_Last
拷贝的截止迭代器 (这里使用{}
占位 即拷贝所有输入流中的字符) - 参数2
_Dest
如何拷贝(这里使用std::inserter
进行插入)
std::inserter
将每一组输入的字符串作为 key
插入到容器中
- 参数1
_Cont
需要插入数据的容器(这里是sets) - 参数2
_Where
需要插入的位置(这里始终插入到sets
的尾部)
运行结果
输入: 1 12 3 3 3 3 3 ^Z
输出: size: 3 [ 1 12 3 ]
set
容器的 key
是不可重复的,如果需要运行重复 key
的 set
可以使用 `std::multiset
set
容器内部通过一颗 R&B树(红黑树)
来存储数据,其对字符串的排序方式是按照 字典序故输出时 12
出现在 3
之前
第四章 兼容迭代器
略过,详细可以点击标题了解。
第五章 lambda表达式
5.3用于作用域可重用代码
#include"print.h"
int main() {
auto one = []() {return "one"; };
auto two = [] {return "two"; };
print("{} {}\n", one(), two());
auto p = [](auto f) {//泛型lambda,C++20之前只能使用这种方式
print("{}\n", f());
};
p([] {return "乐"; });
auto p2 = []<typename T>(T&& f) { print("{}\n", f()); };
p2(one);
p2(std::move(one));
[] <typename T>(T&& f) { print("{}\n", f()); }(two);
int num{};
auto p3 = [num]()mutable {num++; };
for (auto i = 0; i < 5; i++)p3();
print("{}\n", num);
auto p4 = [&]()mutable {num++; };
print("{}\n", sizeof(p4));
constexpr int n = []()constexpr {return 10 * 10; }();
auto p5 = []()->int {return 10; };
}
5.4算法库中作为谓词
#include"print.h"
#include<vector>
bool is_div4(int i) {
return i % 4 == 0;
}
struct is_div4_ {
bool operator()(int i) {
return i % 4 == 0;
}
};
auto is_div_by(int divisor) {
return [=](int i) {return i % divisor == 0; };
}
int main() {
std::vector v{ 1,2,3,4,44,8,10 };
auto count1 =std::count_if(v.begin(), v.end(), is_div4);
auto count2 = std::count_if(v.begin(), v.end(), is_div4_{});
print("{} {}\n", count1, count2);
auto count3 = std::count_if(v.begin(), v.end(), [](int i) {return i % 4 == 0; });
print("{}\n", count3);
for (int i : {3, 4, 5}) {
auto count = std::ranges::count_if(v, is_div_by(i));
print("{} ", count);
}
//不带捕获的lambda表达式可以有转换函数,隐式转换到对应的函数指针
int(*p)(int) = [](int a) {return a; };
print("{}\n", p(10));
}
5.5与std::function
一起作为多态包装器
#include"print.h"
#include<vector>
#include<functional>
#include<list>
#include<deque>
void hello() {
print("hello\n");
}
struct Hello_ {
void greeting() {
print("hello\n");
}
};
int main() {
std::deque<int>d;
std::list<int>l;
std::vector<int>v;
auto print_c = [](const auto& c) {
for (const auto& i : c)print("{} ", i);
print("\n");
};
auto push_c = [](auto& container) {
return [&container](auto value) {
container.push_back(value);
};
};
const std::vector<std::function<void(int)>>consumers{ push_c(d),push_c(l),push_c(v) };
//consumers[0](10);
//print_c(d);
for (auto& i : consumers) {
for (size_t j = 0; j < 10; j++) {
i(j);
}
}
print_c(d);
print_c(l);
print_c(v);
std::function f{ hello };
f();
Hello_ h;
std::function<void(void)>ff{ std::bind(&Hello_::greeting,&h) };
ff();
std::bind(&Hello_::greeting, &h)();
}
5.7将谓词与逻辑连接词连接起来
#include"print.h"
#include <functional>
static bool begins_with_a(const std::string& s)
{
return s.find("a") == 0;
}
static bool ends_with_b(const std::string& s)
{
return s.rfind("b") == s.length() - 1;
}
template <typename A, typename B, typename F>
auto combine(F binary_func, A a, B b) {
return [=](auto param) {
return binary_func(a(param), b(param));
};
}
int main() {
auto a_xxx_b{ combine(std::logical_and<int>{},begins_with_a, ends_with_b) };
std::copy_if(std::istream_iterator<std::string>{std::cin}, {},
std::ostream_iterator<std::string>{std::cout, ", "}, a_xxx_b);
std::cout << '\n';
}
5.8用相同的输入调用多个lambda
#include"print.h"
auto braces(const char a, const char b) {
return [a, b](const auto v) {
print("{}{}{} ", a, v, b);
};
}
int main() {
auto a = braces('(', ')');
auto b = braces('[', ']');
auto c = braces('{', '}');
auto d = braces('|', '|');
for (int i : {1, 2, 3, 4, 5}) {
for (auto x : { a,b,c,d }) {
x(i);
}
print("\n");
}
}
5.9对跳转表使用映射lambda
#include"print.h"
const char prompt(const char* p) {
std::string r;
print("{} > ", p);
std::getline(std::cin, r, '\n');
if (r.size() < 1) return '\0';//如果走这个分支,就是直接下一个循环
if (r.size() > 1) {
print("响应时间过长\n");
return '\0';
}
return toupper(r[0]);
}
int main() {
using jumpfunc = void(*)();
std::map<const char, jumpfunc> jumpmap{
{ 'A', [] { print("func A\n"); } },
{ 'B', [] { print("func B\n"); } },
{ 'C', [] { print("func C\n"); } },
{ 'D', [] { print("func D\n"); } },
{ 'X', [] { print("Bye!\n"); } }
};
char select{};
while (select != 'X') {
if ((select = prompt("select A/B/C/D/X"))) {
auto it = jumpmap.find(select);
if (it != jumpmap.end()) it->second();
else print("没有对应的选项!\n");
}
}
}
第六章 STL算法
6.2基于迭代器的复制
#include"print.h"
#include<vector>
namespace stdr = std::ranges;
int main() {
std::vector<std::string>v1{ "alpha","beta","gamma","delta","epsilon" };
printc(v1,"v1");
std::vector<std::string>v2(v1.size());
std::copy(v1.begin(), v1.end(), v2.begin());
printc(v2, "v2");
std::copy(v1.begin(), v1.end(), std::back_inserter(v2));
printc(v2, "v2");
std::vector<std::string>v3{};
std::copy_n(v1.begin(), 3, std::back_inserter(v3));
printc(v3, "v3");
std::vector<std::string>v4{};
/*std::copy_if(v1.begin(), v1.end(), std::back_inserter(v4), [](auto& s) {
return s.size() > 4;
});*/
stdr::copy_if(v1,std::back_inserter(v4), [](auto& s) {
return s.size() > 4;
});
printc(v4, "v4");
stdr::copy(v1, std::ostream_iterator<std::string>{std::cout, " "});
print("\n");
stdr::move(v1, v2.begin());
printc(v1, "after move: v1");
printc(v2, "after move: v2");
}
6.3将容器元素连接到以供字符串当中
#include"print.h"
#include<vector>
#include<sstream>
#include<list>
#include<numbers>
namespace bw {
template<typename T>
std::ostream& join(T it, T end_it, std::ostream& o, std::string_view sep = "") {
if (it != end_it)o << *it++;
while (it != end_it)o << sep << *it++;
return o;
}
template<typename I>
std::string join(I it, I end_it, std::string_view sep = "") {
std::ostringstream ostr;
join(it, end_it, ostr, sep);
return ostr.str();
}
std::string join(const auto& c, std::string_view sep = "") {
return join(std::begin(c), std::end(c), sep);
}
}
int main() {
std::vector<std::string>greek{ "alpha","beta","gamma",
"delta","epsilon" };
for (const auto& c : greek) std::cout << c << ",";
print("\n");
auto greek_view = greek | std::views::join;
for (const auto& c : greek_view) std::cout << c;
print("\n");
bw::join(greek.begin(), greek.end(), std::cout, ", ") << '\n';
auto s = bw::join(greek.begin(), greek.end(), ", ");
print("{}\n", s);
auto s2 = bw::join(greek, ", ");
print("{}\n", s2);
std::list<double>list{ std::numbers::pi,std::numbers::e,std::numbers::sqrt2 };
print("{}\n", bw::join(list, ": "));
}
6.4std::sort
排序容器元素=
#include"print.h"
#include<vector>
#include<random>
void check_sorted(auto& c) {
if (!std::is_sorted(c.begin(), c.end()))print("un");
print("sorted: ");
}
void printc_(const auto& c) {
check_sorted(c);
for (auto& e : c)print("{} ", e);
print("\n");
}
void randomize(auto& c) {
static std::random_device rd;
static std::default_random_engine rng(rd());
std::shuffle(c.begin(), c.end(), rng);
}
struct things {
std::string s_;
int i_;
std::string str()const {
return std::format("({}, {})", s_, i_);
}
};
void print_things(const auto& c) {
for (auto& v : c)print("{} ", v.str());
print("\n");
}
int main() {
std::vector<int>v{ 1,2,3,4,5,6,7,8,9,10 };
printc_(v);
for (int i{ 3 }; i; i--) {
randomize(v);
printc_(v);
}
std::sort(v.begin(), v.end());
printc_(v);
print("partial_sort:\n");
randomize(v);
auto middle{ v.begin() + (v.size() / 2) };
std::partial_sort(v.begin(), middle, v.end());
printc_(v);
std::partition(v.begin(), v.end(), [](int i) {return i > 5; });
printc_(v);
std::vector<things>vthings{ {"🐴",1},{"😘",2},{"🤣",3},{"🥵",4},{"🤡",5} };
std::sort(vthings.begin(), vthings.end(),
[](const things& lhs, const things& rhs) {
return lhs.i_ > rhs.i_;
});
print_things(vthings);
}
6.5std::transform
修改容器内容
#include"print.h"
#include<vector>
std::string str_lower(const std::string& s) {
std::string outstr{};
for (const char& c : s) {
outstr += tolower(c);
}
return outstr;
}
int main() {
std::vector<int>v1{ 1,2,3,4,5,6,7,8,9,10 };
std::vector<int>v2;
printc(v1, "v1");
std::transform(v1.begin(), v1.end(), std::back_inserter(v2), [](int x) {return x * x; });
printc(v2, "v2");
std::vector<std::string>vstr1{ "Aaa","Bbb","Ccc","DDD" };
std::vector<std::string>vstr2;
printc(vstr1, "vstr1");
print("str_lower:\n");
std::transform(vstr1.begin(), vstr1.end(), std::back_inserter(vstr2),
[](std::string& x) {return str_lower(x); });
printc(vstr2, "vstr2");
print("ranges sequares:\n");
auto view1 = std::views::transform(v1, [](int x) {return x * x; });
printc(view1, "view1");
v2.clear();
std::ranges::transform(v1, std::back_inserter(v2), [](int x) {return x * x; });
printc(v2, "v2");
}
其中一个常用操作就是配合tolower
()函数是把字符串都转化为小写字母;touppre
()函数是把字符串都转化为大写字母:
transform(first.begin(),first.end(),second.begin(),::tolower);
transform(first.begin(),first.end(),second.begin(),::touppre());
6.6查找特定项
#include"print.h"
#include<vector>
#include<algorithm>
struct City {
std::string name{};
unsigned pop{};
bool operator==(const City& o)const {
return name == o.name;
}
std::string str()const {
return std::format("[{}, {}]", name, pop);
}
};
int main() {
const std::vector<int>v{ 1,2,3,4,5,6,7,8,9,10 };
auto it1 = std::find(v.begin(), v.end(), 7);
if (it1 != v.end())print("found: {}\n", *it1);
else print("not found:\n");
const std::vector<City>c{
{"London",8425622},
{"Berlin",3566791},
{"Tokyo",37435191},
{"Cairo",20485965}
};
auto it2 = std::find(c.begin(), c.end(), City{ "Berlin" });
if (it2 != c.end())print("found: {}\n", it2->str());
else print("not found:\n");
auto it3 = std::find_if(begin(c), end(c), [](const City& item) {
return item.pop > 20000000;
});
if (it3 != c.end())print("found: {}\n", it3->str());
else print("not found:\n");
auto vwl = std::views::filter(c, [](const City& item) {
return item.pop > 20000000;
});
for (const City& e : vwl)print("{}\n", e.str());
}
这个内容大概四个部分
- 使用
std::find
查找标量元素 - 使用
std::find
查找自定义类型元素(需要重载operator==
) - 使用
std::find_if
查找自定义类型符合谓词要求的元素 - 使用
std::views::filter
返回符合谓词要求的视图,可以像普通容器一样遍历
std::find
或std::find_if
的返回值是迭代器,如果没有查找到,则返回end()
。
6.10合并已排序容器
#include"print.h"
#include<vector>
#include<algorithm>
int main() {
std::vector<std::string>vs1{ "dog","cat","veloiraptor" };
std::vector<std::string>vs2{ "kirk","sulu","spock" };
std::vector<std::string>dest{};
printc(vs1, "vs1");
printc(vs2, "vs2");
std::ranges::sort(vs1);
std::ranges::sort(vs2);
printc(vs1, "vs1");
printc(vs2, "vs2");
std::merge(vs1.begin(), vs1.end(), vs2.begin(), vs2.end(), std::back_inserter(dest));
printc(dest, "dest");
}
运行结果:
vs1: [dog] [cat] [veloiraptor]
vs2: [kirk] [sulu] [spock]
vs1: [cat] [dog] [veloiraptor]
vs2: [kirk] [spock] [sulu]
dest: [cat] [dog] [kirk] [spock] [sulu] [veloiraptor]
std::merge
算法接受两个已排序的序列,并创建第三个已合并并排序的序列。前面四个参数表示两个输入范围,第五个参数表示结果序列发送的输出迭代器
第七章 字符串、流和格式化
STL 字符串类是一个功能强大的全功能工具,用于存储、操作和显示基于字符的数据。在高级脚本语言中,可以找到的许多字符串相关的便利、快速和敏捷的功能。
std::string
类基于 std::basic_string
,这是一个连续的容器类,可以用字符类型实例化。其类签名是这样
template <class _Elem, class _Traits = char_traits<_Elem>, class _Alloc = allocator<_Elem>>
class basic_string
Trait
和 Allocator
模板参数通常保留默认值。basic_string
的底层存储是一个连续的 CharT 序列,可以通过 data()
成员函数访问:
#include<string>
#include<iostream>
int main() {
const std::basic_string<char>s{ "hello" };
const char* sdata = s.data();
for (size_t i = 0; i < s.size(); i++){
std::cout << sdata[i] << ' ';
}
std::cout << '\n';
}
运行结果:
h e l l o
data()
成员函数返回一个指向底层字符数组的 CharT*
。从 C++11 起,data() 返回的数组以空结 束,使得 data()
等价于 c_str()
。
basic_string
类包含许多在其他连续存储类中可以找到的方法,包括 insert()
、erase()
、push_back()
、 pop_back()
等,这些方法可以操作底层的 CharT 数组。
std::string
是 std::basic_string<char>
类型的别名:
using string = basic_string<char, char_traits<char>, allocator<char>>;
7.3轻量字符串对象string_view
#include"print.h"
#include<string>
using namespace std::literals;
std::string_view sv() {
const char text[]{ "hello" };
std::string_view greeting{ text };
return greeting;
}
void f(const std::string& str) {
}
void f2(std::string_view str) {
}
int main() {
char str[10]{ "hello" };
std::string str2{ str };
print("{}\n", str2);
str[0] = 'a';
print("{}\n", str2);
std::string_view sview{ str };
print("{}\n", sview);
str[0] = 'b';
print("{}\n", sview);
auto t = sv();
print("{}\n", t);
print("{}\n", "hello"sv.substr(1,4));
constexpr std::string_view str3{ "哈哈" };
//constexpr std::string str4{ "哈哈" };//error
print("{}\n", str3);
std::string str4{ "1" };
const std::string str5{ "1" };
f(str4);
f(str5);
f("1");//开销大,需要构造临时的std::string对象
f2("1");
f2(str4);
f2(str5);
}
std::string_view
是C++17添加的一个字符串视图类,它的构成和原理也十分简单:它的构造函数只是把自己的数据成员const pointer
以及size
初始化而已,这是通常的实现,也就是自己不存储任何数据,副本,只是视图,依靠指针进行一切访问操作,不提供修改操作
7.4连接字符串
#include"print.h"
#include<sstream>
#include<ostream>
#include<chrono>
using std::chrono::high_resolution_clock;
using std::chrono::duration;
void timer(auto(f)()->std::string) {
auto t1 = high_resolution_clock::now();
std::string s{ f() };
auto t2 = high_resolution_clock::now();
duration<double, std::milli>ms = t2 - t1;
print("{}", s);
print("duration: {} ms\n", ms.count());
}
std::string concat_string() {
print("concat_string\n");
std::string a{ "a" };
std::string b{ "b" };
long n{};
while (++n) {
std::string x{};
x += a + ", " + b + "\n";
if (n >= 10000000)return x;
}
return "error\n";
}
std::string append_string() {
print("append_string\n");
std::string a{ "a" };
std::string b{ "b" };
long n{};
while (++n) {
std::string x{};
x.append(a);
x.append(", ");
x.append(b);
x.append("\n");
if (n >= 10000000)return x;
}
return "error\n";
}
std::string concat_ostringstream() {
print("ostringstream\n");
std::string a{ "a" };
std::string b{ "b" };
long n{};
while (++n) {
std::stringstream x{};
x << a << ", " << b << "\n";
if (n >= 10000000)return x.str();
}
return "error\n";
}
std::string concat_format() {
print("append_format\n");
std::string a{ "a" };
std::string b{ "b" };
long n{};
while (++n) {
std::string x{};
x += std::format("{}, {}\n", a, b);
if (n >= 10000000)return x;
}
return "error\n";
}
int main() {
timer(append_string);
timer(concat_string);
timer(concat_ostringstream);
timer(concat_format);
}
运行结果:
append_string
a, b
duration: 5285.7537 ms
concat_string
a, b
duration: 19286.9228 ms
ostringstream
a, b
duration: 21790.0884 ms
append_format
a, b
duration: 29601.7629 ms
7.5转换字符串
#include"print.h"
char char_upper(const char& c) {
return static_cast<char>(std::toupper(c));
}
char char_lower(const char& c) {
return static_cast<char>(std::tolower(c));
}
char rot13(const char& x) {
auto rot13a = [](char x, char a)->char {
return a + (x - a + 13) % 26;
};
if (x >= 'A' && x <= 'Z')return rot13a(x, 'A');
if (x >= 'a' && x <= 'z')return rot13a(x, 'a');
return x;
}
std::string title_case(std::string& s) {
auto begin{ s.begin() };
auto end{ s.end() };
*begin++ = char_upper(*begin);
bool space_flag{ false };
for (auto it{ begin }; it != end; ++it) {
if (*it == ' ')space_flag = true;
else {
if (space_flag)*it = char_upper(*it);
space_flag = false;
}
}
return s;
}
int main() {
std::string s{ "hello jimi\n" };
print("{}", s);
std::transform(s.begin(), s.end(), s.begin(), char_upper);
print("{}", s);
for (auto& c : s)c = rot13(c);
print("{}", s);
for (auto& c : s)c = rot13(char_lower(c));
print("{}", s);
title_case(s);
print("{}", s);
}
运行结果:
hello jimi
HELLO JIMI
URYYB WVZV
hello jimi
Hello Jimi
7.7删除字符串的空白
#include"print.h"
#include<ranges>
std::string trimstr(const std::string& s) {
constexpr const char* whitespace{ " \t\r\n\v\f" };
if (s.empty())return s;
const auto first{ s.find_first_not_of(whitespace) };
if (first == std::string::npos)return{};
const auto last{ s.find_last_not_of(whitespace) };
return s.substr(first, (last - first + 1));
}
int main() {
std::string s{ " \t ten-thumbed input \t \n \t" };
print("[{}]\n", s);
print("[{}]\n", trimstr(s));
}
运行结果:
[ ten-thumbed input
]
[ten-thumbed input]
7.9统计文件中的单词数
#include"print.h"
#include<fstream>
#include<filesystem>
size_t wordcount(auto& is) {
using it_t = std::istream_iterator<std::string>;
return std::distance(it_t{ is }, {});
}
int main() {
const char* fn{ "E:/自制视频教程/《C++20 STL Cookbook》2023/src/src/the-raven.txt" };
std::ifstream infile{ fn,std::ios_base::in };
size_t wc{ wordcount(infile) };
print("There are {} words in the file.\n", wc);
print("size: {}\n", std::filesystem::file_size(fn));
}
第八章 实用工具类
C++标准库包括为特定任务设计的各种工具类。有些是常见的,读者们可能在这本书的其他示 例中见过很多这样的类。 本章在以下主题中介绍了一些通用的工具,包括时间测量、泛型类型、智能指针等:
• std::optional
管理可选值
• std::any
保证类型安全
• std::variant
存储不同的类型
• std::chrono
的时间事件
• 对可变元组使用折叠表达式
• std::unique_ptr
管理已分配的内存
• std::shared_ptr
的共享对象
• 对共享对象使用弱指针
• 共享管理对象的成员
• 比较随机数引擎
• 比较随机数分布发生器
感兴趣的可以点击标题去看具体内容
第九章 并发和并行
并发性和并行性指的是在不同的执行线程中运行代码的能力。并发性 是在后台运行线程的能力,并行性 是在处理器的不同内核中同时运行线程的能力。 运行时库以及主机操作系统,将为给定硬件环境中的线程,在并发和并行执行模型之间进行选择。 在现代多任务操作系统中,main()
函数已经代表了一个执行线程。当一个新线程启动时,可由 现有的线程派生。 C++ 标准库中,std::thread
类提供了线程执行的基本单元。其他类构建在线程之上,以提供锁、 互斥和其他并发模式。根据系统架构的不同,执行线程可以在一个处理器上并发运行,也可以在不 同的内核上并行运行。
• 休眠一定的时间
• std::thread
——实现并发
• std::async
——实现并发(重点看一下)
• STL 算法与执行策略
• 互斥锁和锁——安全地共享数据(重点看一下)
• std::atomic
——共享标志和值
• std::call_once
——初始化线程
• std::condition_variable
——解决生产者-消费者问题
• 实现多个生产者和消费者
第十章 文件系统
不多介绍