cfl997 (CaoFulei) · GitHub
Books-Goals/Modern-cpp-tutorial现代c++教程.txt at main · cfl997/Books-Goals · GitHub
20230229-20230317
2023 01 29
11 nullptr NULL 0
11 constexpr 编译器能够在编译时就把这些表达式直接优化并植入到程序运行时
14 内存使用局部变量
2023 01 30
17 if switch 中 临时变量
11 初始化列表
11 std::tuple std::tie
17 结构化绑定
std::tuple f(){
return std::make_tuple(1,2.3,"123");
}
auto [x,y,z]=f();
std::cout<::value
20 auto 函数传参 不能用于推导数组类型
11 尾返回类型 auto
template
auto add2(T x, U y) -> decltype(x+y){
return x + y;
}
14 template
auto add3(T x, U y){
return x + y;
}
14 decltype(auto) 主要用于对转发函数或封装的返回类型进行推导,
它使我们无需显式的指定 decltype 的参数表达式
decltype(auto) look_up_a_string_1() {
return lookup1();
}
17 constexpr 这个关键字引入到 if 语句中 让代码在编译时就完成分支判断
if constexpr (std::is_integral::value) {
return t + 1;
} else {
return t + 0.001;
}
11 for (auto data : vec) 区间for迭代
11 using name = oldname typedef 原名称 新名称
2023 01 31
11 变长参数模板 template class Magic;
允许任意个数、任意类别的模板参数,同时也不需要在定义时将参数的个数固定
函数参数 也使用同样的表示法代表不定长参数
template void printf(const std::string &str, Args... args);
解包
1.递归模板函数
不断递归地向函数传递模板参数,进而达到递归遍历所有模板参数的目的
#include
template
void printf1(T0 value) {
std::cout << value << std::endl;
}
template
void printf1(T value, Ts... args) {
std::cout << value << std::endl;
printf1(args...);
}
int main() {
printf1(1, 2, "123", 1.1);
return 0;
}
2.变参模板展开
17
template
void printf2(T0 t0, T... t) {
std::cout << t0 << std::endl;
if constexpr (sizeof...(t) > 0) printf2(t...);
}
3.初始化列表展开
template
auto printf3(T value, Ts... args) {
std::cout << value << std::endl;
(void) std::initializer_list{([&args] {
std::cout << args << std::endl;
}(), value)...};
}
17 折叠表达式
#include
template
auto sum(T ... t) {
return (t + ...);
}
int main() {
std::cout << sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) << std::endl;
}
2023 02 01
11 非模板类型推导
template
class buffer_t {
public:
T& alloc();
void free(T& item);
private:
T data[BufSize];
}
buffer_t buf; // 100 作为模板参数
17 auto 作为非模板类型推导
template void foo() {
std::cout << value << std::endl;
return;
}
int main() {
foo<10>(); // value 被推导为 int 类型
}
11 委托构造 构造函数可以在同一个类中一个构造函数调用另一个构造函数
#include
class Base {
public:
int value1;
int value2;
Base() {
value1 = 1;
}
Base(int value) : Base() { // 委托 Base() 构造函数
value2 = value;
}
};
11 继承构造
class Subclass : public Base {
public:
using Base::Base; // 继承构造
};
11 显示虚函数重载 override final
override 关键字将显式的告知编译器进行重载,
编译器将检查基函数是否存在这样的虚函数,否则将无法通过编译
final 则是为了防止类被继续继承以及终止虚函数继续重载引入的
11 显示禁用默认函数
class Magic {
public:
Magic() = default; // 显式声明使用编译器生成的构造
Magic& operator=(const Magic&) = delete; // 显式声明拒绝编译器生成构造
Magic(int magic_number);
}
11 强类型枚举 enum class
enum class new_enum : unsigned int {
value1,
value2,
value3 = 100,
value4 = 100
};
不能够被隐式的转换为整数,同时也不能够将其与整数数
字进行比较,更不可能对不同的枚举类型的枚举值进行比较。
但相同枚举值之间如果指定的值相同,那么可以进行比较:
if (new_enum::value3 == new_enum::value4) {
// 会输出
std::cout << "new_enum::value3 == new_enum::value4" << std::endl;
}
显示的进行类型转换->获得枚举值
#include
template
std::ostream& operator<<(
typename std::enable_if::value,
std::ostream>::type& stream, const T& e)
{
return stream << static_cast::type>(e);
}
11 Lambda表达式
[捕获列表](参数列表) mutable(可选) 异常属性 -> 返回类型 {
// 函数体
}
值捕获= 引用捕获& 隐式捕获[]
表达式捕获 (捕获右值)
#include
#include // std::make_unique
#include // std::move
void lambda_expression_capture() {
auto important = std::make_unique(1);
auto add = [v1 = 1, v2 = std::move(important)](int x, int y) -> int {
return x+y+v1+(*v2);
};
std::cout << add(3,4) << std::endl;
}
在上面的代码中, important 是一个独占指针,是不能够被 “=” 值捕获到,这时候我们可以将其转
移为右值,在表达式中初始化。
14 泛型Lambda auto
auto add = [](auto x, auto y) {
return x+y;
};
add(1, 2);
add(1.1, 2.2);
11 函数对象包装器 std::function 是一种通用、多态的函数封装,它的实例可以对任何可以调用的目标实体进
行存储、复制和调用操作。
函数的容器。
11 std::bind函数的意义就如字面上的意思一样,用来绑定函数调用的某些参数。
std::bind的思想其实是一种延迟计算的思想,将可调用对象保存起来,然后在需要的时候再调用。而且这种绑定是非常灵活的,不论是普通函数还是函数对象还是成员函数都可以绑定,而且其参数可以支持占位符。
这里的std::placeholders::_1是一个占位符,且绑定第一个参数,若可调用实体有2个形参,那么绑定第二个参数的占位符是std::placeholders::_2。
int foo(int a, int b, int c) {
;
}
int main() {
auto bindFoo = std::bind(foo, std::placeholders::_1, 1,2);
// 这时调用 bindFoo 时,只需要提供第一个参数即可
bindFoo(1);
}
11 右值引用 右边的值,是指表达式结束后就不再存在的临时对象。
纯右值、将亡值。
将亡值 (xvalue, expiring value),是 C++11 为了引入右值引用而提出的概念。
将亡值就定义了这样一种行为:临时的值能够被识别、同时又能够被移动。
std::vector foo() {
std::vector temp = {1, 2, 3, 4};
return temp;
}
std::vector v = foo();
右值引用和左值引用
要拿到一个将亡值,就需要用到右值引用: T &&。
右值引用的声明让这个临时值的生命周期得以延长、只要变量还活着,那么将亡值将继续存活。
11 提供了 std::move 这个方法将左值参数无条件的转换为右值。
// int &a = std::move(1); // 不合法,非常量左引用无法引用右值
const int &b = std::move(1); // 合法, 常量左引用允许引用右值
移动语义 std::move 不会出现拷贝行为
std::string str = "Hello world.";
std::vector v;
// 将使用 push_back(const T&), 即产生拷贝行为
v.push_back(str);
// 将输出 "str: Hello world."
std::cout << "str: " << str << std::endl;
// 将使用 push_back(const T&&), 不会出现拷贝行为
// 而整个字符串会被移动到 vector 中,所以有时候 std::move 会用来减少拷贝出现的开销
// 这步操作后, str 中的值会变为空
v.push_back(std::move(str));
// 将输出 "str: "
std::cout << "str: " << str << std::endl;
引用坍缩规则:在传统 C++ 中,我们不能够对一个引用类型继续进行引用
完美转发 std::forward
就是为了让我们在传递参数的时候,保持原来
的参数类型(左引用保持左引用,右引用保持右引用)。
为了解决这个问题,我们应该使用 std::forward 来进行参数的转发(传递):
无论传递参数为左值还是右值,普通传参都会将参数作为左值进行转发,所以 std::move 总会接受
到一个左值,从而转发调用了 reference(int&&) 输出右值引用。
唯独 std::forward 即没有造成任何多余的拷贝,同时完美转发 (传递) 了函数的实参给了内部调用
的其他函数。
std::forward 和 std::move 一样,没有做任何事情, std::move 单纯的将左值转化为右值,
std::forward 也只是单纯的将参数做了一个类型的转换,从现象上来看, std::forward(v) 和
static_cast(v) 是完全一样的。
11 std::array
std::vector v; capacity 成倍申请内存后并没有被释放。
v.shrink_to_fit(); 返回给系统
std::array arr = {1, 2, 3, 4};
std::sort(arr.begin(), arr.end(), [](int a, int b) {
return b < a;
});
void foo(int *p, int len) {return;}
std::array arr = {1,2,3,4};
// C 风格接口传参
// foo(arr, arr.size()); // 非法, 无法隐式转换
foo(&arr[0], arr.size());
foo(arr.data(), arr.size());
11 std::forward_list 单向链表 0(1)复杂度插入 list 双向链表
有序容器 std::map /std::set 红黑树 插入搜索 o(log(size))
11 无序容器 Hash表 插入搜索 o(constant)
std::unordered_map/std::unordered_multimap
std::unordered_set/std::unordered_multiset
11 元组 tuple
1. std::make_tuple: 构造元组
2. std::get: 获得元组某个位置的值
3. std::tie: 元组拆包
auto student = std::make_tuple(3.8, ’A’, " 张三");
std::get<0>(student);
double gpa;char grade; std::string name;
std::tie(gpa, grade, name) = student;
14 根据使用类型 获得元组中的对象
std::tuple t("123", 4.5, 6.7, 8);
std::cout << std::get(t) << std::endl;
std::cout << std::get(t) << std::endl; // 非法, 引发编译期错误
std::cout << std::get<3>(t) << std::endl;
std::tuple t("123", 4.5, 6.7, 8);
17 运行期索引 std::variant<>
std::get的局限
int index = 1;
std::get(t);
上面这个不合法。
#include
template
constexpr std::variant _tuple_index(const std::tuple& tpl, size_t i) {
if constexpr (n >= sizeof...(T))
throw std::out_of_range(" 越界.");
if (i == n)
return std::variant{ std::in_place_index, std::get(tpl) };
return _tuple_index<(n < sizeof...(T)-1 ? n+1 : 0)>(tpl, i);
}
template
constexpr std::variant tuple_index(const std::tuple& tpl, size_t i) {
return _tuple_index<0>(tpl, i);
}
template
std::ostream & operator<< (std::ostream & s, std::variant const & v) {
std::visit([&](auto && x){ s << x;}, v);
return s;
}
这样就可以
int i = 1;
std::cout << tuple_index(t, i) << std::endl;
元组合并和遍历 tuple_cat
auto student = std::make_tuple(3.8, ’A’, " 张三");
auto new_tuple = std::tuple_cat(student, std::move(t));
template
auto tuple_len(T &tpl) {
return std::tuple_size::value;
}
// 迭代
for(int i = 0; i != tuple_len(new_tuple); ++i)
// 运行期索引
std::cout << tuple_index(new_tuple, i) << std::endl;
RAII和引用计数
11 std::shared_ptr/std::unique_ptr/std::weak_ptr
std::make_shared
auto pointer = std::make_shared(10);
std::shared_ptr 可以通过 get() 方法来获取原始指针,通过 reset() 来减少一个引用计数,并
通过 use_count() 来查看一个对象的引用计数。
std::unique_ptr pointer = std::make_unique(10); // make_unique 从 C++14 引入
make_unique 并不复杂, C++11 没有提供 std::make_unique,可以自行实现:
template
std::unique_ptr make_unique( Args&& ...args ) {
return std::unique_ptr( new T( std::forward(args)... ) );
}
可以使用std::move 转移给其他 unique_ptr
std::weak_ptr (弱引用)
当A、B两个std::shared_ptr互相引用时,析构无法释放资源,引用计数各为1。
弱引用不会引起计数增加
可以std::shared_ptr 是否存在,其 expired() 方法能在资源未被释放时,会返回 false,否则返回 true;
它也可以用于获取指向原始对象的 std::shared_ptr 指针,其 lock() 方法在原始对象未被释放时,
返回一个指向原始对象的 std::shared_ptr 指针,进而访问原始对象的资源,否则返回nullptr。
正则表达式 : 正则表达式必知必会
新书地址: https://forta.com/books/0134757068/
https://regex101.com/
https://www.regextester.com/
.
\.
[a-z][A-z]
[^0-9] ^
window : \r\n\r\n
linux: \n\n
\r 回车
\n 换行
\d [0-9]
\D [^0-9]
\w [a-zA-Z0-9_]
\W [^a-zA-Z0-9_]
\s 空白字符
\S 非空白字符
+ 匹配1+
* 匹配0+
? 匹配0或1
{} 匹配多少个 {1,2}{2,4} {3,}必须3次或以上
*? +? {n,}? 懒惰型元字符
\b 单词边界
\B 没有单词边界
() 子表达式
#include
#include
#include
int main() {
std::string fnames[] = {"foo.txt", "bar.txt", "test", "a0.txt", "AAA.txt"};
// 在 C++ 中 \ 会被作为字符串内的转义符,
// 为使 \. 作为正则表达式传递进去生效,需要对 \ 进行二次转义,从而有 \\.
std::regex txt_regex("[a-z]+\\.txt");
for (const auto &fname: fnames)
std::cout << fname << ": " << std::regex_match(fname, txt_regex) << std::endl;
}
result: 1 1 0 0 0
std::regex base_regex("([a-z]+)\\.txt");
std::smatch base_match;
for(const auto &fname: fnames) {
if (std::regex_match(fname, base_match, base_regex)) {
// std::smatch 的第一个元素匹配整个字符串
// std::smatch 的第二个元素匹配了第一个括号表达式
if (base_match.size() == 2) {
std::string base = base_match[1].str();
std::cout << "sub-match[0]: " << base_match[0].str() << std::endl;
std::cout << fname << " sub-match[1]: " << base << std::endl;
}
}
}
result :
sub-match[0]: foo.txt
foo.txt sub-match[1]: foo
sub-match[0]: bar.txt
bar.txt sub-match[1]: bar
并行与并发
11 mutex
static std::mutex mtx;
11 std::lock_guard lock(mtx);
aaa(int i){}
std::thread t1(aaa,2),t2(aaa,3);
t1.join();
t2.join();
std::unique_lock
std::lock_guard 不能显式的调用 lock 和 unlock,
而 std::unique_lock 可以在声明后的任意位置调用,可以缩小锁的作用范围,提供更高的并发度
void critical_section(int change_v) {
static std::mutex mtx;
std::unique_lock lock(mtx);
// 执行竞争操作
v = change_v;
std::cout << v << std::endl;
// 将锁进行释放
lock.unlock();
// 在此期间,任何人都可以抢夺 v 的持有权
// 开始另一组竞争操作,再次加锁
lock.lock();
v += 1;
std::cout << v << std::endl;
}
11 期物(Future)表现为std::future,它提供了一个访问异步操作结果的途径
C++11 提供的std::future 简化了这个流程,可以用来获取异步任务的结果。
自然地,我们很容易能够想象到把它作为一种简单的线程同步手段,即屏障(barrier)。
使用std::packaged_task,它可以用来封装任何可以调用的目标,从而用于实现异步的调用
#include
#include
#include
int main() {
// 将一个返回值为7 的lambda 表达式封装到task 中
// std::packaged_task 的模板参数为要封装函数的类型
std::packaged_task task([](){return 7;});
// 获得task 的期物
std::future result = task.get_future(); // 在一个线程中执行task
std::thread(std::move(task)).detach();
std::cout << "waiting...";
result.wait(); // 在此设置屏障,阻塞到期物的完成
// 输出执行结果
std::cout << "done!" << std:: endl << "future result is "
<< result.get() << std::endl;
return 0;
}
在封装好要调用的目标后,可以使用get_future() 来获得一个std::future 对象,以便之后实施
线程同步。
11 std::condition_variable 条件变量
条件变量 std::condition_variable 是为了解决死锁而生,当互斥操作不够用而引入的。
比如,线程可能需要等待某个条件为真才能继续执行,而一个忙等待循环中可能会导致所有其他线程都无法进入临界区使得条件为真时,就会发生死锁。
所以, condition_variable 实例被创建出现主要就是用于唤醒等待线程从而避免死锁。
std::condition_variable 的 notify_one() 用于唤醒一个线程;
notify_all()则是通知所有线程。
#include
#include
#include
#include
#include
#include
int main()
{
std::queue produced_nums;
std::mutex mtx;
std::condition_variable cv;
bool notified = false; // 通知信号
// 生产者
auto producer = [&]() {
for (int i = 0; ; i++) {
std::this_thread::sleep_for(std::chrono::milliseconds(900));
std::unique_lock lock(mtx);
std::cout << "producing " << i << std::endl;
produced_nums.push(i);
notified = true;
cv.notify_all(); // 此处也可以使用 notify_one
}
};
// 消费者
auto consumer = [&]() {
while (true) {
std::unique_lock lock(mtx);
while (!notified) { // 避免虚假唤醒
cv.wait(lock);
}
// 短暂取消锁,使得生产者有机会在消费者消费空前继续生产
lock.unlock();
// 消费者慢于生产者
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
lock.lock();
while (!produced_nums.empty()) {
std::cout << "consuming " << produced_nums.front() << std::endl;
produced_nums.pop();
}
notified = false;
}
};
// 分别在不同的线程中运行
std::thread p(producer);
std::thread cs[2];
for (int i = 0; i < 2; ++i) {
cs[i] = std::thread(consumer);
}
p.join();
for (int i = 0; i < 2; ++i) {
cs[i].join();
}
return 0;
}
原子操作
std::mutex 可以解决上面出现的并发读写的问题,但互斥锁是操作系统级的功能,这是因为一个互
斥锁的实现通常包含两条基本原理:
1. 提供线程间自动的状态转换,即『锁住』这个状态
2. 保障在互斥锁操作期间,所操作变量的内存与临界区外进行隔离
11 std::atomic
std::atomiccounter;
#include
#include
#include
std::atomic count = {0};
int main() {
std::thread t1([](){
count.fetch_add(1);
});
std::thread t2([](){
count++; // 等价于 fetch_add
count += 1; // 等价于 fetch_add
});
t1.join();
t2.join();
std::cout << count << std::endl;
return 0;
}
并非所有的类型都能提供原子操作,这是因为原子操作的可行性取决于具体的 CPU 架构,
以及所实例化的类型结构是否能够满足该 CPU 架构对内存对齐条件的要求,因而我们总是可以通过
std::atomic::is_lock_free 来检查该原子类型是否需支持原子操作,例如:
std::atomic::is_lock_free
#include
#include
struct A {
float x;
int y;
long long z;
};
int main() {
std::atomic a;
std::cout << std::boolalpha << a.is_lock_free() << std::endl;
return 0;
}
一致性模型 4种
提速 削弱原子操作的在进程间的同步条件。
从原理上看,每个线程可以对应为一个集群节点,而线程间的通信也几乎等价于集群节点间的通信。
削弱进程间的同步条件,通常我们会考虑四种不同的一致性模型:
1、线性一致性
t1 -------------+1------------+load()-----
t2 -------------------+2------------------
难以实现 必须顺序执行
2、顺序一致性
t1 -------------+1-------+3----+load()----
t2 -------------------+2------------------
或
t1 -------------+1--------+3----+load()---
t2 ----------+2---------------------------
2在3前即可
3、因果一致性
t1 -------------a=1-------b=2--------------------------------------
t2 --------x.store(3)----------c=a+b-----y.load()------------------
或
t1 -------------a=1-------b=2--------------------------------------
t2 --------x.store(3)---------------y.load()------c=a+b------------
或
t1 -------------b=2----------------a=1-----------------------------
t2 -----------------y.load()------------c=a+b--------x.store(3)----
整个过程中,只有c对a和b产生依赖,而x和y在此例子中表现为没有关系
(但实际情况中我们需要更详细的信息才能确定 x 与 y 确实无关)
4、最终一致性
是最弱的一致性要求,它只保障某个操作在未来的某个时间节点上会被观察到,但并
未要求被观察到的时间
t1 --------x.store(3)-------------x.stor(4)------------------------------------
t2 -----------------x.read()------------x.read()--------x.read()----x.read()---
3444
0344
0004
0000
11 内存顺序 6种
1、宽松模型:
在此模型下,单个线程内的原子操作都是顺序执行的,不允许指令重排,但不同线程间
原子操作的顺序是任意的。类型通过 std::memory_order_relaxed 指定。我们来看一个例子:
std::atomic counter = { 0 };
std::vector vt;
for (int i = 0; i < 100; ++i) {
vt.emplace_back([&]() {
counter.fetch_add(1, std::memory_order_relaxed);
});
}
for (auto& t : vt) {
t.join();
}
std::cout << "current counter:" << counter << std::endl;
2、释放/消费模型:
在此模型中,我们开始限制进程间的操作顺序,如果某个线程需要修改某个值,但
另一个线程会对该值的某次操作产生依赖,即后者依赖前者。具体而言,线程 A 完成了三次对 x 的
写操作,线程 B 仅依赖其中第三次 x 的写操作,与 x 的前两次写行为无关,则当 A 主动 x.release()
时候(即使用 std::memory_order_release),选项 std::memory_order_consume 能够确保 B 在
调用 x.load() 时候观察到 A 中第三次对 x 的写操作。我们来看一个例子:
// 初始化为 nullptr 防止 consumer 线程从野指针进行读取
std::atomic ptr(nullptr);
int v;
std::thread producer([&]() {
int* p = new int(42);
v = 1024;
ptr.store(p, std::memory_order_release);
});
std::thread consumer([&]() {
int* p;
while (!(p = ptr.load(std::memory_order_consume)));
std::cout << "p: " << *p << std::endl;
std::cout << "v: " << v << std::endl;
});
producer.join();
consumer.join();
p:42
v:1024
3、释放/获取模型
在此模型下,我们可以进一步加紧对不同线程间原子操作的顺序的限制,在释放
std::memory_order_release 和获取 std::memory_order_acquire 之间规定时序,即发生在释放
(release)操作之前的所有写操作,对其他线程的任何获取(acquire)操作都是可见的,亦即发生顺
序(happens-before)。
可以看到, std::memory_order_release 确保了它之前的写操作不会发生在释放操作之后,是一
个向后的屏障(backward),而 std::memory_order_acquire 确保了它之前的写行为不会发生在
该获取操作之后,是一个向前的屏障(forward)。对于选项 std::memory_order_acq_rel 而言,则
结合了这两者的特点,唯一确定了一个内存屏障,使得当前线程对内存的读写不会被重排并越过此
操作的前后:
std::vector v;
std::atomic flag = { 0 };
std::thread release([&]() {
v.push_back(42);
flag.store(1, std::memory_order_release);
});
std::thread acqrel([&]() {
int expected = 1; // must before compare_exchange_strong
while (!flag.compare_exchange_strong(expected, 2, std::memory_order_acq_rel))
expected = 1; // must after compare_exchange_strong
// flag has changed to 2
});
std::thread acquire([&]() {
while (flag.load(std::memory_order_acquire) < 2);
std::cout << v.at(0) << std::endl; // must be 42
});
release.join();
acqrel.join();
acquire.join();
在此例中我们使用了 compare_exchange_strong 比较交换原语(Compare-and-swap primitive),
它有一个更弱的版本,即 compare_exchange_weak,它允许即便交换成功,也仍然返回 false
失败。其原因是因为在某些平台上虚假故障导致的,具体而言,当 CPU 进行上下文切换时,另
一线程加载同一地址产生的不一致。除此之外, compare_exchange_strong 的性能可能稍差于
compare_exchange_weak,但大部分情况下,鉴于其使用的复杂度而言, compare_exchange_weak
应该被有限考虑。
4、顺序一致模型
在此模型下,原子操作满足顺序一致性,进而可能对性能产生损耗。可显式的通过
std::memory_order_seq_cst 进行指定。
std::atomic counter = { 0 };
std::vector vt;
for (int i = 0; i < 100; ++i) {
vt.emplace_back([&]() {
counter.fetch_add(1, std::memory_order_seq_cst);
});
}
for (auto& t : vt) {
t.join();
}
std::cout << "current counter:" << counter << std::endl;
这个例子与第一个宽松模型的例子本质上没有区别,仅仅只是将原子操作的内存顺序修改为了
memory_order_seq_cst,有兴趣的读者可以自行编写程序测量这两种不同内存顺序导致的性能差异。
文件系统(待完善)
11 noexcept
1. 函数可能抛出任何异常
2. 函数不能抛出任何异常
noexcept 修饰完一个函数之后能够起到封锁异常扩散的功效,如果内部产生异常,外部也不会触发。
11 原始字符串字面量
C++11 提供了原始字符串字面量的写法,可以在一个字符串前方使用 R 来修饰这个字符串,同时,
将原始字符串使用括号包裹,例如:
#include
#include
int main() {
std::string str = R"(C:\File\To\Path)";
std::cout << str << std::endl;
return 0;
}
11 自定义字面量 重载双引号后缀运算符
// 字符串字面量自定义必须设置如下的参数列表
std::string operator"" _wow1(const char *wow1, size_t len) {
return std::string(wow1)+"woooooooooow, amazing";
}
std::string operator"" _wow2 (unsigned long long i) {
return std::to_string(i)+"woooooooooow, amazing";
}
int main() {
auto str = "abc"_wow1;
auto num = 1_wow2;
std::cout << str << std::endl;
std::cout << num << std::endl;
return 0;
}