C++ 大 学 习
更一些比较短小有意义的的东西 长篇的内容转到CSDN
CSDN:
返回值优化
返回值优化 | RVO | NRVO |
---|---|---|
全称 | Return Value Optimization | Named Return Value Optimization(对于具名 |
原理 | 返回一个类对象的函数的返回值当做该函数的参数(T&)来处理 | 对于具名变量的增强 |
初始化
所有具有静态存储期的非局部变量的初始化会作为程序启动的一部分在 main 函数的执行之前进行(除非被延迟,见下文)。所有具有线程局部存储期的非局部变量的初始化会作为线程启动的一部分进行,按顺序早于线程函数的执行开始。
一段能在 main
函数之前执行的代码
#include <iostream>
int test() {
std::cout << "Before Main" << std::endl;
return 1;
}
int v = test();
int val = []() {
std::cout << "Lambda" << std::endl;
return 0;
}();
int main() {
std::cout << "main" << std::endl;
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
延迟动态初始化
还看不懂
random
C++11之前只有srand/rand
(<stdlib.h>
)可以来获取均匀分布的随机数
在<random>
(Since C++11)中 随机数的获取分为了一组协作的类
- 随机数引擎(Engines) --> 生成随机的
unsigned
序列 - 随机数分布(Distribution) --> 使用
Engine
返回服从特定分布的随机数
随机数(伪随机数)产生的原理为 对于给定的初始值(seed) 经过计算 得到非周期性的结果
C-like
标准库提供一个默认的随机数引擎std::default_random_engine
它的具体类型取决与编译器
可以这样实现一个random
函数 std::default_random_engine
的一个有参构造函数指定了初始化时的随机数种子
#include<random>
unsigned _your_random(){
static std::default_random_engine dre;
return dre();
}
2
3
4
5
引擎
引擎(Engine
)类有一些通用操作
Engine::result_type
引擎使用和生成数值的类型Engine e;
Engine e(seed);
e.seed(_seed);
e.min/max();
上下确界e.discard(ull);
丢弃多个随机数
// discard 的实现
void discard(unsigned long long __z){
for (; __z != 0ULL; --__z)(*this)();
}
2
3
4
更多引擎类
引擎模板 C++11实现了三种引擎模板
linear_congruential_engine
: 实现线性同余算法mersenne_twister_engine
: 实现梅森缠绕器算法subtract_with_carry_engine
: 实现带进位减(一种延迟斐波那契)算法
几种预定义的随机数生成器(充满了Magic Number)
using minstd_rand0 = std::linear_congruential_engine<std::uint_fast32_t, 16807, 0, 2147483647>; // 1988年的最小标准
using minstd_rand = std::linear_congruential_engine<std::uint_fast32_t, 48271, 0, 2147483647>; // 1993年的最小标准
using mt19937 = std::mersenne_twister_engine<std::uint_fast32_t, 32, 624, 397, 31,
0x9908b0df, 11,
0xffffffff, 7,
0x9d2c5680, 15,
0xefc60000, 18, 1812433253>; // 32bit 梅森缠绕器
using mt19937_64 = std::mersenne_twister_engine<std::uint_fast64_t, 64, 312, 156, 31,
0xb5026f5aa96619e9, 29,
0x5555555555555555, 17,
0x71d67fffeda60000, 37,
0xfff7eee000000000, 43, 6364136223846793005>; // 64bit 梅森缠绕器
using ranlux24_base = std::subtract_with_carry_engine<std::uint_fast32_t, 24, 10, 24>;
using ranlux48_base = std::subtract_with_carry_engine<std::uint_fast64_t, 48, 5, 12>;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
分布
分布(Dist
)类有一些通用操作
Dist d;
其他构造函数根据具体的分布类而定d(e);
获取服从该分布的随机数d.min/max();
上下确界d.reset();
重置分布状态 消除之前产生数据的影响
均匀分布代码示例
#include<random>
#include<iostream>
int main(){
std::default_random_engine dre;
std::uniform_int_distribution<unsigned> uniform(0,19); // 创建闭区间 [0, 19] 上的分布
std::uniform_real_distribution<float> ur(0,1); // 区间 [a, b) 上的均匀分布
for(int i=0;i<10;++i){
std::cout<<uniform(dre)<<" "<<std::ends;
}
return 0;
}
2
3
4
5
6
7
8
9
10
11
伯努利分布示例
#include<random>
#include<iostream>
template<typename func>
void repeat(unsigned times,func fn){
for(;times!=0;--times)fn();
}
int main(){
std::default_random_engine dre;
std::bernoulli_distribution bd;
int count = 0;
repeat(1e5,[&](){count+=bd(dre);});
std::cout<<count<<"/"<<1e5<<"="<<count/1e5<<std::endl; // 可能结果 49922/100000=0.49922
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
所有的分布类别
- 均匀分布
- 伯努利分布
- 泊松分布
- 正态分布
- 采样分布
特殊设施
std::random_device
:生成非确定随机数的均匀分布整数随机数生成器 通常仅用于播种类似 mt19937 的伪随机数生成器
std::seed_seq
:消耗整数值随机数列 并产生无符号数值
#include <random>
#include <cstdint>
#include <iostream>
int main(){
std::seed_seq seq{1,2,3,4,5};
std::vector<std::uint32_t> seeds(10);
seq.generate(seeds.begin(), seeds.end());
for (std::uint32_t n : seeds) {
std::cout << n << '\n';
}
}
2
3
4
5
6
7
8
9
10
11
12
move semantics
TIP
右值引用是 移动构造和移动拷贝 的基础
这三者是移动语义实现的底层
C++11 新增
- 右值引用
- 移动语义
- 完美转发(forward)
C++11 划分右值为两种
- 将亡值(xvalue, expiring value)
- 纯右值(pvalue, pure value)
g++会省略创建一个只是为了初始化另一个同类型对象的临时对象
-fno-elide-constructors
来关闭该优化-O0
是优化缺省值 并不能关闭优化
#include <iostream>
#include <string>
static std::ostream& println(std::string const& msg) {
return std::cout << msg << std::endl;
}
class Int {
int* v;
public:
Int(int val) : v(new int(val)) { println("ctor"); }
Int(Int const& val) : v(new int(*val.v)) { println("ref ctor"); }
Int(Int&& val) : v(val.v) {
val.v = nullptr;
println("rref ctor");
}
void printThis()const{
printf("%p\n",this);
}
void printInt()const{
printf("%p\n",this->v);
}
Int& operator=(const Int& iv) {
v = new int(*iv.v);
println("copy");
return *this;
}
Int& operator=(Int&& iv) {
v = iv.v;
iv.v = nullptr;
println("r copy");
return *this;
}
~Int(){
if(v)delete v;
println("dtor");
}
};
Int ret(){Int a(0); return a; }
void xvalue(){
/**
* #1 Int a(0) ctor
* #2 return a;-> 一个临时对象 rref ctor
* #1 dctor
* #3 a 移动构造 rref ctor
* #2 dctor
*/
Int a = ret();
println("\tBefore b");
Int&& b = ret(); // 延续返回的临时对象的生命周期
println("\tBefore End");
}
void printThis(){
Int&& a = [](){Int a(0);a.printThis();return a;}();
println("");
a.printThis();
}
void printInt(){
Int&& a = [](){Int a(0);a.printInt();return a;}();
println("");
a.printInt();
}
void move(){
Int a(42);
a.printInt();
Int b = std::move(a);
a.printInt();
b.printInt();
}
#include<vector>
int main() {
move();
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
以下均展示关闭构造函数优化后的结果 不同标题名代表不同的函数
xvalue
xvalue() 结果
ctor
rref ctor
dtor
rref ctor
dtor
Before b
ctor
rref ctor
dtor
Before End
dtor
dtor
2
3
4
5
6
7
8
9
10
11
12
printThis
你无法获取局部对象 这是肯定的
printThis() 结果
ctor
0x7ffc643384b0
rref ctor
dtor
0x7ffc643384f0
dtor
2
3
4
5
6
7
printInt
但是你可以一直持有原来的资源 这是能办到的
printInt() 结果
ctor
0x55b5773e9eb0
rref ctor
dtor
0x55b5773e9eb0
dtor
2
3
4
5
6
7
move
std::move
只是单纯的类型转换 lvalue -> rvalue
但是能让你简单的资源挪位 而非重新构造
move() 结果
ctor
0x558542ebbeb0
rref ctor
(nil)
0x558542ebbeb0
dtor
dtor
2
3
4
5
6
7
std::forward
template<typename T>
void callG(T&& val){ // 涉及知识点 引用折叠
g(std::forward<T>(val));
}
2
3
4
推导
typeof
gcc only
C++11标准前 类似decltype
功能的运算符
typeid
获取目标操作数类型的运算符 返回的是左值 只会涉及基本类型 不包含CV限定符和引用
auto
- 对于变量 从它的初始化器推导出它的类型(Since C++11)
- 对于函数 从它的return语句推导出类型(Since C++14)
- 对于非类型模板形参 指定要从实参推导出它的类型(Since C++20)
可配合cv限定符和&
/*
(引用/指针)这样的修饰符一起使用
decltype
- 如果实参是没有括号的标识表达式或类成员访问表达式 那么
decltype
产生以该表达式命名的实体的类型- 如果没有这种实体或该实参指名了一组重载函数 那么程序非良构(即错误的)
- 如果实参是指名某个结构化绑定的没有括号的标识表达式 那么
decltype
产生其被引用类型(Since C++17) - 如果实参是指名某个非类型模板形参的没有括号的标识表达式 那么
decltype
生成该模板形参的类型(当该模板形参以占位符类型声明时,类型会先进行任何所需的类型推导)(Since C++20)
- 如果实参是其他类型为
T
(可以是不完整类型) 的任何表达式- 如果 表达式 的值类别是亡值 将会
decltype->T&&
- 如果 表达式 的值类别是左值 将会
decltype->T&
- 如果 表达式 的值类别是纯右值 将会
decltype->T
- 如果 表达式 是返回类类型纯右值的函数调用 或是右操作数为这种函数调用的逗号表达式 那么不会对该纯右值引入临时量 (Before C++17)
- 如果 表达式 是~~除了(可带括号的)立即调用以外的 (Since C++20)~~纯右值,那么不会从该纯右值实质化临时对象:即这种纯右值没有结果对象。(Since C++17)
- 如果 表达式 的值类别是亡值 将会
- 如果对象的名字带有括号 那么它会被当做通常的左值表达式 从而
decltype(x)
和decltype((x))
通常是不同的类型
对于第一条规则
#include<type_traits>
#include<bits/move.h>
int main(){
int i = 0;
int* j=&i;
int n[10];
decltype(i=0); // int& // 但不会赋值(Clang)
// rule 1
static_assert(std::is_same_v<decltype(i = 0), int&>);
static_assert(std::is_same_v<decltype(n[5]), int&>);
static_assert(std::is_same_v<decltype(*j), int&>);
static_assert(std::is_same_v<decltype(static_cast<int&&>(i)), int&&>);
static_assert(std::is_same_v<decltype(i++), int>);
static_assert(std::is_same_v<decltype(++i), int&>);
// rule 2
static_assert(std::is_same_v<decltype(std::move(i)), int&&>); // xvalue
static_assert(std::is_same_v<decltype(0, i), int&>); // comma/lvalue
static_assert(std::is_same_v<decltype(i, 0), int>); // rvalue
static_assert(std::is_same_v<decltype("hello world"), const char(&)[12]>); // lvalue
// rule 3
static_assert(std::is_same_v<decltype((i)), int&>);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
cv限定符
通常情况下 decltype
会推导出cv限定符
但当表达式是成员变量时 父对象的限定符会被忽略
struct A {
double x;
};
const A* a = new A();
static_assert(std::is_same_v<decltype(a->x),double>);
static_assert(std::is_same_v<decltype((a->x)),const double&>);
2
3
4
5
6
但多加一层括号会改变结果
decltype(auto)
Since C++14
- 只能独立使用(不能搭配cv限定 指针/引用)
- 使
auto
推导使用decltype
方式