C++ Cookbook by Eric

1. C++ environment: g++-11

关于C++的编程环境配置,请参考博文《【C++学习笔记】C++编程环境配置》

2. IDE

关于C++使用的IDE配置,请参考博文《【C++】IDE Cookbook by Eric》

3. Basics

请参考博文《【C++】基础知识的学习笔记》

4. Philosophy

请参考博文《【C++】Philosophy》

5 File

.h:C++头文件。
.cc:CUDA语境下的CPU代码文件。
.cu:CUDA语境下的GPU代码文件。

5.1 头文件

5.1.1 引入头文件

C++在引入头文件时,有下面两种方式:
1. <file_name>:仅适用于标准库文件的引入,例如

#include <iostream>

2. file_name.h:适用于自定义的头文件、以及兼容C方式的头文件,例如

#include <iostream.h>
#include <cuda_runtime_api.h>
#include "custom_api.h"

Note:此外,为了与C语言兼容,C++在将原有C语言头文件标准化后,头文件前会带有c字母,如cstdiocstringctimectype等等。于是,当我们要用C++标准化了的C语言头文件时,可以进行如下的转换:

#include <stdio.h>	--> #include <cstdio>
#include <stdlib.h>	--> #include <cstdlib>
#include <string.h>	--> #include <cstring>

5.1.2 头文件一次编译声明

使用#ifndef & #define & #endif防止声明冲突

使用#ifndef & #define & #endif可以防止重复声明,这是因为如果不使用文件标识符的话,多次引用相同头文件会重复声明某些相同的字段,而引起冲突;

# pragma once:对"#ifndef & #define & #endif"的升级宏定义

5.1.3 头文件推荐用法

头文件通常包含以下内容:

  • 常量定义
  • 函数声明
  • 类、结构体、枚举类型的声明
  • 模板声明
  • 命名空间声明
  • 宏定义
“将函数定义放在头文件中并使用 # pragma once” 是一种推荐的做法吗?

将函数的定义放在头文件中并使用#pragma once是可以减少重复编译的问题,但并不是一种推荐的做法:

  • #pragma once 指令是告诉编译器只包含这个头文件一次。它可以防止头文件被重复包含,从而减少编译时间。
  • 然而,将函数的定义放在头文件中会导致程序空间浪费,因为每个源文件都会包含函数的定义。所以,推荐的做法是在头文件中只包含函数的声明,将函数的定义放在单独的源文件中。这样,编译器只需要在每个源文件中编译一次函数的定义,减少编译时间,并且只有一个函数实体,节省空间,(具体来说,会减小可执行文件的体积)。

6. Naming style: small camel-case

6.1 常量名

枚举元素命名跟变量名类似 - [Guide]

使用小写单词;

6.2 变量名

第一个单词的字母全部小写,第二个单词的首字母大写;

6.3 类名

跟变量名类似;
缩写单词全部大写,后如果跟一个单词的话,第一个字母仍然大写;
例如:TextView, JSONArray;

6.2.1 数据成员:小写+下划线

7. Type & Data

7.1 类型声明模板

类型模板:

const decltype(auto)

引用模板:

const auto&

7.2 Const:常量限定符

关于const的作用,我感觉菜鸟教程的解释是很好理解的,这里引用一下:

C++ const 允许指定一个语义约束,编译器会强制实施这个约束,允许程序员告诉编译器某值是保持不变的。如果在编程中确实有某个值保持不变,就应该明确使用const,这样可以获得编译器的帮助

7.2.1 constconst int是两种不同的类型

示例代码:[Cpp_const_int]

7.2.2 Constexpr:编译期常量表达式

constexpr表示可以在编译时确定值的常量;
Note:constexpr是一种对常量定义的声明,但并非是一种类型关键字,
在这里插入图片描述
可以看到在进行类型判断时,编译器认为constexpr并不是有效的类型关键字。

使用#defineconstexpr定义常量有什么区别呢?

#define写起来会更加简单一些,不需要加上类型声明;而且constexpr的主要应用并不是用来定义数值常量,这一点我们是从cppreference的示例中感受到的,这些示例中目前还没有使用constexpr定义数值常量的例子;

7.3 Variable Type

关于常见的扩展数据类型,请参考博文《c++基础之uint8_t》
uint8_t: unsigned char,无符号字符类型。
size_t: 在C++用于描述大小的类型,常用于数组索引和循环计数。

025在C++中表示“025八进制”
在 C++ 中,整数字面量以0开头时,表示该数字使用八进制。
也就是说,025在C++中表示八进制025,也就是十进制21。

7.3.1 auto自动类型

Note
在使用auto自动声明变量类型时,会出现类型退化的现象,不过C++标准并没有规定具体的“退化规则”,所以这需要我们通过实验的方式进行验证。

7.3.2 确定变量类型

使用decltype()确定表达式类型
std::is_same_v<decltype(var), type>

Note:
is_same_v()是对is_same()函数的简化,也就是输出时不用加上::value来获得bool值结果了。

使用 C++ Insights 确定表达式类型

示例代码:《C++ Insights: int& y》

为什么将std::is_same_v<decltype(var), type>的写法简化成std::is_type_same<type>(var)是比较困难的呢?

主要原因是,某些类型的变量之间本身可以相互赋值而无法有效区分,例如:Type a | Type &a | const Type a | const Type &a。SO上面曾尝试给出一个解答,但是在类型const int&上就验证失败了【测试 is_same】

7.4 类型转换

基础类型转换模板:<new_type>{varible}
Note:这里使用{}避免调用C风格转换。

7.4.1 隐式类型转换

Type conversionsupport
double → int
禁止隐式的收缩转换

赋值时不允许收缩转换:x = {y}
(替代赋值语句 x = y
对于

  1. y是立即数或者编译期常量,例如:x = {3}或者constexpr int y = 3; x = {y},编译器会检查数值是否符合x的类型;
  2. y是运行期变量,编译期会检查 y⇒x 是否存在类型收缩,如果存在则拒绝赋值操作然后报错。

7.3 传值和传址

右值引用

Note
关于为什么引入右值引用,请参考视频教程《[C++] 为什么需要右值引用?》

Reference Collapsing:引用折叠

Reference Collapsing 是C++中的一种类型转换规则。它是在 C++11 标准中引入的,用于解决引用嵌套类型的问题。当一个引用类型嵌套在另一个引用类型中时,“reference collapsing” 将自动应用,并使嵌套的引用类型转换为非引用类型。
例如,T& & 转换为 T&,T& && 转换为 T&,T&& & 转换为 T&,T&& && 转换为 T&&。

7.5 类型别名:using MyType = type

示例:using MyInt = int;

7.6 Initialization

书写形式中文名称
{}列表初始化列表(list initialization)

Note:
C++标准 明确表示了braced-init-list不是一种表达式,请参见 List-initialization - cppreference.com

学习教程

  1. 关于使用() | {}进行初始化的习惯用法,请参考《C++中五花八门的初始化》
  2. 关于C++标准中初始化的设计理念,请参考《【C++】CppCon 2018: Nicolai Josuttis “The Nightmare of Initialization in C++” -知识点目录》

7.3 Copy operation

std::move(v):临时拷贝

#include <utility>

std::move(v)可以生成一个匿名的临时拷贝的对象;

7.4 间接访问(Indirection)

不能定义引用类型的vector

int a = 1;
std::vector<int&> vec_try;	//错误,不能定义引用类型的vector
std::vector<int*> vec;		//正确,可以定义指针类型的vector
vec.push_back(&a);

7.5 智能指针

7.5.1 Unique_ptr

可以使用=运算符实现类对象的正常析构 ⇒ [Cpp_unique_ptr]

7.5.2 Shared_ptr

模板:

auto p = std::make_shared<Type>(...);
使用 shared_ptr 作为类数据成员的指针不是推荐的做法

因为 shared_ptr 最主要的作用是进行内存资源的自动释放,而类数据成员的内存资源是由类本身负责管理的,并不需要 shared_ptr 来进行管理,所以不推荐使用 shared_ptr 来标记类对象的数据成员;
Note:直接使用C风格指针是推荐的做法。

7.5.3 Weak_ptr:防止循环引用而引入的智能指针

Weak_ptr可以解决循环引用的问题,其中,循环引用问题(也称为 Resource Loops 问题)是指:当两个对象中分别包含指向对方的 shared_ptr 时,会出现循环计数的现象,导致两个都无法由 shared_ptr 自动销毁,相应代码请参考[Cpp_resource_loops_with_shared_ptr]

Note:
这里,我们也找到了一种比较简单的解决方案(请参见[Cpp_release_shared_ptr]),就是在 shared_ptr 释放前调用reset()消除引用计数,就可以解开两个对象成员之间的循环引用,从而进行正常的析构了。

8. Operator

8.1 运算符优先级

在CLion中,可以使用快捷键ctrl+W来确定表达式的计算顺序,
示例:对于以下代码,我们想看?==的执行顺序,可以看到此时光标位于a的右侧,
在这里插入图片描述
此时使用ctrl+W,会选中a变量
在这里插入图片描述
再次按下ctrl+W,则会选中a = 42
在这里插入图片描述
说明赋值表达式会先执行。

8.2 赋值操作符:=

结构化绑定:Structured binding

代码示例:[Cpp_structured_binding_demo]

int n = 3;
std::string s = "123";
auto tpl = std::make_tuple(n,s);

8.2 算术操作符

加法

expr_a + expr_b:C++标准不保证expr_aexpr_b在计算时的先后顺序。

8.3 自增/自减运算符:self increment or decrement: ++x | --x

AttributePrefixSuffix
return操作数x原始值
返回值性质前缀时返回左值后缀时返回右值

8.3.1 性能分析

前缀运算(++x)的性能常常优于后缀运算(x++

这是因为后缀运算可能会涉及到新对象的构造操作;

9. Data container

9.1 Enum

9.2 Array

Auto数组引用:auto &a

9.2.1 C-style array: a[N]

在C++中,C-style数组是以 row-major 的方式存储的,即每一行的数据存储在连续的内存块中。

Note
C++标准并没有明确规定 C-style array 的layout,而对此 GCC Manual 也没有明确说明;不过按照指针地址的一般定义,数组的内存访问顺序是row-major的。

对于二维数组,第一个维度用Row表示,第二个维度用Column表示,(跟矩阵的表示相同)

Note
数组读取操作一般比写入操作快10倍以上。

9.2.2 Std::array

优点
  • stdArr.at(i)在超过数组下标时,程序会直接报错,而不是返回未定义值或者程序崩溃;
将数组转换为std::array

C++20: std::to_array

int s[3] = { 1, 5, 8 };
auto a = std::to_array(s);

By C++17:
关于在C++17语境中将C数组[]转换为std::array,请参考博文《在 C++ 中将 C 样式数组转换为 std::array 容器》

9.2 List: std::list

关于list的初始化,请参考《在 C++ 中初始化一个 std::list》

9.3 Vector: std::vector

std::vector可以同时支持索引访问和迭代访问;
std::vector的元素是存储在堆内存上;

Note
vector内部是使用array实现的,在进行中间插入时速度较慢,如果需要频繁地进行中间插入,可以考虑使用list

预申请数组空间:vec.reserve(n)

如果提前知道数组最大长度范围的话,可以使用vec.reserve()预先申请内存空间;

添加元素:vec.emplace_back()

一般情况下,emplace_back()的速度要优于push_back(),(关于两者速度的小测试,可以参考博文《6 Tips to supercharge C++11 vector performance》);

获得底层数组指针:vec.data()

获取vec长度

vec.size(); //返回vec中元素的个数 

9.4 Dictionary: std::map

ValueType: std::pair<const Key, T>

ValueType不加const会引发pair复制

具体可以参考[C++_insights_map_no_const]
在这里插入图片描述
通过 C++ Insights 解析代码后可以看到会有一个std::pair<int, int>(m.begin().operator*())对象构造的操作,而并不是直接使用m.begin().operator*()取地址后获得的对象;

访问不存在的key会返回0

[Cpp_not_existed_key]
在这里插入图片描述

Note
在这个示例中,访问不存在的键值"hye",不仅会返回0,而且会插入键值对{"hye": 0},此时字典的长度为2。

9.5 Unordered containers

C++标准中还提供了set和map的unordered实现,即:unordered_set和unordered_map,在某些情况,例如:一次性输入大量数据进行初始化,后续多次进行查找操作的场景,可以提供更快的查找速度;
相关的知识请参考《深蓝学院C++课程:第10章: 序列与关联容器 - 第3节: 关联容器 - 69:unordered set / map / multiset / multimap》

9.5.1 给自定义类建立unordered_map

请参考博文《How to use Unordered_set with User defined classes – Tutorial & Example》

9.6 Ranges&views:高阶函数(C++20

请参考博文《【C++】Ranges&views(高阶函数)的学习笔记》

9.7 遍历容器

9.7.1 翻转遍历容器请使用std::ranges::reverse_view()C++20

std::ranges::reverse_view()可以创建一个视图用来反向遍历容器,其中view的含义是创建视图对象,也就是说,不会改变原始容器的顺序;(请参考[Cpp20_reverse_view]

9.8 归约计算

数组求和

#include <iostream>
#include <numeric>

int main() {
    int nums[] = {1, 2, 3, 4, 5};

    auto sum = std::accumulate(std::cbegin(nums),
    std::cend(nums), 0);

    std::cout << "Sum: " << sum << std::endl; // 输出 15
    return 0;
}

10. Control flow

10.1 循环语句

读取模板:

for (const auto& item: container) {
    cout << item << endl;
}

修改元素模板(使用万能引用):

for (auto&& item: container) {
    cin >> item;
}

循环条件为变量声明:“类似于python中海象表达式的操作”

比如,有时候循环条件的值我们也希望在循环体中使用,则可以在循环条件求值时同时将条件表达式赋予给一个变量,从而避免重复的逻辑,可以参考cppreference中遍历字符数组的示例

8.2 If-else语句

If-else在匹配时,只会match最近的一个语句,如果需要匹配多个语句,则需要加上{}表示一个语句块;

[[likely]] & [[unlikely]] 优化

Note
[Chatty]:
需要注意的是,[[likely]][[unlikely]]属性只是给编译器提供了一些提示,编译器并不一定会采取相应的优化策略。在实际使用中,应该先进行性能测试,再决定是否使用这些属性来优化代码。

8.3 Switch语句

警告提示隐式的 fall through 行为:

# g++编译选项
-Wimplicit-fallthourgh

8.4 For&switch的高级应用:达夫设备

深蓝学院C++课程:第6章: 语句 - 第4节: 语句的综合应用——达夫设备

11. Function

Stability Notes

I. 实参到形参的拷贝求值顺序不确定(C++标准对此没有规定)

在函数调用执行时,(实参虽然使用逗号,隔开),不过实参表达式的执行顺序是不确定的;

11.1 Main函数

程序退出推荐使用return而不是std::exit()函数

程序中止模板:

return EXIT_FAILURE;

Note:
经过查阅资料,我们知道使用return语句退出会比直接调用std::exit()退出会更加安全一些;因为在使用exit(0)退出程序时,不会调用局部作用域非静态对象的析构函数,(于是某些资源可能会没有被正常释放),请参考文章《return statement vs exit() in main()》

11.2 函数说明符(specifier)

Inline:内联函数

内联函数最好声明在同一个翻译单元内,例如:一个cpp中,或者一个cpp及其h文件组成翻译单元中。
在这里插入图片描述

Constexpr & consteval

Constexpr:常量计算函数

Constexpr声明的函数既可以在编译期执行,也可以在运行期执行;
运行期执行示例:[Cpp_constexpr_run]

Consteval (C++20):将可推导结果函数代码编译为立即数

Prerequisite: gcc >= 10.1
不同于constexpr的宽松规定,consteval声明的函数仅能在编译期执行,不能在运行期执行;

Explicit:禁止函数的隐式调用

关于explicit的作用讲解,请参考博文《C++ explicit 关键字》

[[nodiscard]]:函数返回值需要被显示接收

常见用例:函数的功能是动态内存申请,而返回内存指针

如果调用函数分配了内存,函数返回结果指向了一块内存,如果将返回的指针丢弃,则会造成内存泄露

11.3 形式参数

11.3.1 形参模板

形参模板(≥C++20):
const auto param

Note:
I. const auto& param不够稳定
这是因为如果传入的参数是一个将亡值(xvalue),那么用引用接收参数会存在绑定失效的bug。
II. const decltype(auto) param编译无法通过
即使使用-std=c++23,也无法进行编译;

形参模板(C++17):
const type param

11.3.2 形参赋值的退化现象

典型的示例是一维数组退化成指针类型:
在这里插入图片描述

11.3.3 可变长形参

std::initializer_list:相同类型参数列表

模板:std::initializer_list<type> listParams

Note:
一般来说,使用 const std::initializer_list &par 也是可以合法的方式,但是这种用法不多,因为std::initializer_list<type> listParams内部是基于指针实现的,(即是传入的是对象列表),开销也很小(内部不会有拷贝赋值的操作),所以一般不需要通过使用引用来节省开销,直接使用模板声明的形式就可以了。

使用container.emplace传递参数列表会比使用 initializer_list 性能更好

使用emplace将元素添加到容器中会比使用 initializer_list 更高效,因为它避免了对象的拷贝操作;
具体示例,请参考博文《std::initializer_list in C++ 2/2 - Caveats and Improvements - 2. The cost of copying elements》

11.3.4 检查形参状态:Contracts

Contracts(合约)是C++20的语言特性,可以被视为“assert”语句的增强版,它可以声明函数的前置条件和后置条件;
在这里插入图片描述

11.4 返回值

11.4.1 返回值模板

auto& func(...) {...}

返回引用避免复制构造的开销;

11.4.2 如何将构造的对象传递到函数域的外面

猜想一:可以直接用基类作为返回值类型吗?

测试结果:不行。
测试代码:Cpp_return_with_base
代码的输出:

Called Room::copy_ctor()
Base prints name

可以看到,返回值在回传的时候,出现了一次拷贝构造的过程,于是在main()函数获得的其实是基类的对象,于是无法使用虚函数的动态绑定功能,所以不能直接使用基类作为返回值类型,来传递函数内构造的派生类对象

11.4.3 Initializer_list不适合作为返回值类型

因为如果返回的是 underlying array (“底层数组”),可以认为是一个临时对象,之前说过 initializer_list 是基于指针实现的,而指针无法延长临时对象的生命周期,所以这就是一个典型的不安全的case, 在这里插入图片描述
所以如果要返回列表,推荐返回类型vector<>

11.5 函数重载

11.6 调用解析:the resolution of a function call

解析过程:

  1. Name lookup:查找名称符合的函数,包括Argument-dependent lookup (ADL)
  2. Template argument deduction:模板参数推导
  3. Overload resolution:重载决议

Argument-dependent lookup (ADL):实参依赖查找

The compiler will look for the function in the namespaces that contain the types of the function arguments;例如:在实参对应的类域内部进行查找

Overload resolution: If there are multiple functions with the same name, the compiler will choose the best match based on the types of the arguments and the template arguments, if any.

Access control: The compiler will check if the selected function is accessible from the current scope.

Virtual function dispatch: If the function is a virtual function, the compiler will determine the actual function to be called at runtime based on the dynamic type of the object.

Function call: Finally, the compiler will generate code to call the selected function.
文档:Overload resolution - cppreference.com

11.7 函数指针

using K = int(int)
int inc (int x) {
	return x + 1;
}

void Demo(K* input) {}
void Demo(K input) {}

Demo(inc);
Demo(&inc);

以上高阶函数定义和调用任意组合,效果都是完全相同的,其本质原因是函数类型跟数组类型一样,不支持复制

11.8 递归函数

递归函数可以使用auto作为返回值

示例代码:[Cpp_factorial]

decltype(auto) factorial(auto n) {
  if (n <= 1) {
    return 1;
  }
  return n * factorial(n - 1);
}

11.9 Lambda表达式

《【C++】Lambda Expression 的学习笔记》

11.10 语法限制

(1)C++不允许在函数内部定义函数

例如,用户不能在main()函数的函数体中定义一个函数;

12. Class

定义域:全局域或者局部域。
例如:可以在main()函数内部定义一个类。

class 派生类名: 继承方式(访问说明) 基类名1, 继承方式 基类名2, ..., 继承方式 基类名n
{
	派生类成员声明	
};

Note
关于访问说明对继承基类成员的影响,请参考文章《C++ 公共、受保护和私有继承》

12.1 初始化

缺陷记录

(1)对于声明初始化,C++标准也不允许使用auto来声明数据类型

即使声明初始化给出了数据成员的默认值,也无法使用auto进行声明,
示例:[Cpp_error_auto_member]

12.2 构造函数

12.2.1 复制构造函数(拷贝构造函数)

Copy constructor是指形参为类引用类型的构造函数;
Copy constructor template:

A(const A &)

8.1.2 移动构造函数:节省内存资源的初始化操作

移动构造函数主要解决了资源转移的冗余性问题:对于某些类型,如果拷贝一个对象,会产生额外的资源开销,例如拷贝动态内存等。移动构造函数可以将资源直接从旧对象转移到新对象,从而避免重复申请和拷贝;
Move constructor template:

A(A&& o)

8.1.3 Delegate constructor 与 member initializer list 不能同时使用

因为委托构造函数(delegate constructor)只能单独使用,请参考测试代码[Cpp_delegate constructor_and initializer list]

12.2 赋值函数:本质是一种成员函数

Move assignment operator function template:

struct Str {
    Str& operator=(Str &&x) // this-> m ; x->m
    {
        if (&x == this) // 自身赋值优化
            return *this;
        ...
    }
    ...
};

移动赋值也可以使用default

// ...
// Move constructor
Widget(Widget&&w)=default;
// Move assignment operator
Widget& operator=(Widget&& w)=default;

12.3 析构函数

12.3.1 基类析构函数

写作模板:

virtual ~Base() = default;
写作说明

virtual:当存在子类时,如果使用父类标记接收了子类变量,在析构时,标记自动寻找子类的析构函数并执行,不然只对父类成员进行析构。
= default:此写法跟{}在使用上几乎没有明显的区别,不过可读性会更清晰一些。

12.4 函数禁止:delete

delete的作用是删除函数的调用入口;

Delete “复制构造函数” (copy constructor)

在这里插入图片描述

Delete “移动构造函数” (move constructor)

在这里插入图片描述

Make class instance not copyable and not movable

我们在dali-doc中看到这样的代码:

Dummy(const Dummy&) = delete;
Dummy& operator=(const Dummy&) = delete;
Dummy(Dummy&&) = delete;
Dummy& operator=(Dummy&&) = delete;

其目的是使得Dummy算子实例无法被复制或者转移;
经过查询资料,我们知道在以下场景中可能出现这种情况:

(一)所有权不允许转移:

This class has all copy and move constructors and assignments deleted which makes it not copyable and not movable. As for why I can’t speak of because I am not familiar with the lib, but presumably it models some form of unique non-transferable ownership.

(二)保证资源引用的唯一性(单例模式)

  • Objects sometimes represent actual resources (or ownership) of resources of which there can be only one. Copying those instances would not be right. Deleting the move constructor (and assignment) clearly models that you can’t transfer ownership to some other bit of code (and you want to keep control over the objects lifecycle at all the time).
  • 对象表示一个独占的文件,不希望被拷贝成两个对象。

12.5 成员函数

12.5.1 只读函数:const

声明此函数只能被const的对象指针调用(包括引用产生的指针调用);

12.5.2 函数覆写:override

模板:

bool ActualFunc() const override{
	return true;
}

写作说明:
override:这里使用override显式声明此函数是对基类函数的覆写;
const:显式声明此函数是非原位修改式的函数。

12.6 成员访问:. | ->

使用成员访问操作符访问时:

  • 左操作数是左值(或右值)时,返回左值(或右值, xalue)
  • ->的左操作数是指针,返回左值

12.6.1 使用std::bind + &Str::mem构造成员调用

构造调用时第二个参数可直接填写类对象

可以不加&符号,推测是内部会自动将类对象转换成对应的引用形参,
在这里插入图片描述

12.6.2 std::mem_fn:将类成员函数指针包装成可调用对象

在这里插入图片描述

13. Expression

请参考博文《【C++】Expression的学习笔记》

14. Template

请参考博文《【C++】模板的学习笔记》

15. Metaprogramming

C++中的元编程是指利用编译器执行编译期操作的编程技术。元编程主要通过使用模板(template)和编译期常量(constexpr)来实现,可以在编译期操作类型、执行简单的逻辑运算等。
C++ 中的元编程常用于以下场景:

  • 类型操作:可以在编译期操作类型,例如求类型的大小,构造类型的别名等。
  • 编译期逻辑:可以在编译期执行简单的逻辑运算,例如判断两个类型是否相同。
  • 提高代码效率:可以通过元编程提高代码效率,例如预处理常量表等。

16. Reflection

请参考博文《【C++】Reflection的学习笔记》

17. CMake compiling

请参考博文《CMake Cookbook by Eric》

18. 打印输出

将bool值打印为 true | false

std::cout << std::boolalpha;

17. IO Stream

请参考博文《【C++】IO Stream的学习笔记》

18. 代码调试

请参考博文《【C++】代码调试的学习笔记》

19. Exception handling

请参考博文《【C++】异常处理的学习笔记》

21. 性能优化

21.1 CPU计算性能优化原理

关于在C++中对CPU计算性能优化的原理,请参考视频教程《Branchless Programming in C++ - Fedor Pikus - CppCon 2021》

21.2 编译优化:-O3

我们可以配置编译器选项来对代码进行编译优化

21.2 函数优化:constexpr

constexpr说明符可以使函数在编译期求值,从而优化程序的性能;

23. Python使用C++扩展联合调试

请参考博文《【DALI笔记】Python调用C++扩展库联合调试的学习笔记》

24. DALI custom operator

请参考博文《DALI Cookbook by Eric》

25. C++包管理:conda

可以使用conda来实现C++的环境管理,trtpy就是使用这种方式实现C++环境隔离的;

26. 文本处理

请参考博文《【C++】文本处理的学习笔记》

27. 线程管理

延时函数

线程休眠:std::this_thread::sleep_for()

示例:
线程休眠1秒

std::this_thread::sleep_for(1s);

28. 控制台输入

读取一行输入(包括空行)

string line;
getline(cin, line);

29. 随机数生成:#include <random>

请参考博文《【C++学习笔记】随机数生成:#include <random>》

30. STL标准算法

请参考《【C++】STL标准算法库的学习笔记》

31. 第三方扩展库

请参考博文《【C++】常见第三方扩展库的学习笔记》

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值