constexpr
//递归版本
int fib(int n){
if (n<=2) return n;
return fib(n-1) + fib(n-2)
}
优化方式 动态规划 可以做到O(N)的算法,如果想做到O(1),那么可以把计算消耗挪到编译期间,通过模板也可以实现
template<int n>
struct FIB{
enum {
result = FIB<n-1>::result + FIB<n-2>::result,
};
};
template<>
struct FIB<1>{
enum {
result = 1,
};
};
template<>
struct FIB<2>{
enum {
result = 2,
};
};
在来个编译期间的例子
template<int x,int y>
struct IF{
enum {
result = 0,
};
};
template<int x>
struct IF<x,x>{
enum {
result = 1,
};
};
可以发现
- 有些计算可以挪到编译期间计算
- 所有计算都可以编译期间计算吗?(确实,因为模板是图灵完备的)
- 所有的基本的表达式都可以用模板一一对应
- 值,函数,控制语句也可以
- etc。。
c++11中还有关键字constexpr
template<int x,int y>
struct FUN{
enum {
result = x+y,
};
};
等价于 同样编译期计算
constexpr int FUN(int x,int y){
return x + y;
}
定义变量
struct SomeVal{
enum {
value = 1,
};
};
constexpr int value = 1;
分支语句
if constexpr(x == y){
}
else{}
用constexpr来实现fib数列
constexpr int fib(int n){
return n<=2?n:fib(n-1)+fib(n-2);
}
constexpr类似语法糖一样的东西,可以简化写法
模板类型推倒也有一定的局限性,模板参数必须要能在编译期间计算出来的,返回类型只能是普通数字类型
template<int x,int y>
struct ADD{
enum {
result = x + y,
};
};
//这个如果传递进来是编译可以推倒出来的,那么走模板这种
//否则退化成普通c函数,运行时开销
constexpr add(int x,int y){
return x+y;
}
int x=1,y=2;
cout<<ADD<x,y>::result; //编译错误
add(x,y); //运行时计算
add(1,2); //编译期计算
constexpr 修饰的函数
- 返回类型必须字面值LiteralType
- 参数必须字面值
LiteralType
比如 12,'c'
- cv void
- 基本数字类型
- 引用类型
- Literaltype的数组
针对class类型
- 有Trivial的析构函数 =default
- 至少有1个constexpr 构造函数,不是拷贝或移动构造
- 所有的非静态成员和基类必须都是非volatile的literalType
class LiteralType : BaseLiteralType{
constexpr LiteralType{...} //不是拷贝或者移动构造
LiteralType1 mem1;
LiteralType2 mem2;
}
在编译期间如果能出触发constexpr LiteralType的版本,那么就会走到编译期间的版本
struct conststr {
const char* p; //满足条件1
std::size_t sz; //满足条件1
template<std::size_t N>
constexpr conststr(const char(&a)[N]) : p(a), sz(N - 1) {} //3
//以上三条满足就是个LiteralType
constexpr char operator[](std::size_t n) const {
return n < sz ? p[n] : throw std::out_of_range("");
}
constexpr std::size_t size() const { return sz; }
}
conststr cstr{"hello"}; // a literalType 编译期构造对象
char a[] = "hello";
conststr cstr{a}; // not literalType
constexpr std::size_t countlower(conststr s) {
std::size_t c = 0;
for (int i = 0; i < s.size(); i++) {
if (s[i] >= 'a' && s[i] <= 'z') {
c++;
}
}
return c;
}
conststr s{"hello"};
countlower(s); //编译期计算
char cs[] = "hello";
conststr ns{cs};
countlower(ns); //运行时计算
怎么确定constexpr 定义的函数是在运行期计算还是编译期计算呢
template <int x>
struct EmptyCls {}
//查看这个有没有编译出错
EmptyCls<func_to_check(5)>();
if constexpr
template<class T>
std::string str(T t){
if(std::is_same_v<T,std::string>)
return t;
else
return std::to_string(t);
}
str("11"s); //编译出错
//编译的时候 需要if 和 else分支同时编译 std::to_string 参数不能传递string就会编译不过
所以需要修改上述代码为
template<class T>
std::string str(T t){
if constexpr (std::is_same_v<T,std::string>)
return t;
else
return std::to_string(t);
}
//如果传递可以string进来 那么就不会编译else分支
str(100);
//相反如果传递int进来等价
template<class T>
std::string str(T t){
return std::to_string(t);
}
lambda表达式
- 常用例子
bool cmp(float a,float b){
return a < b;
}
float arr[5] = {1.0,-1.0,2.0,-2.0,0};
//版本1
std::sort(arr,arr + sizeof(arr) / sizeof(decltype(arr[0])), &cmp);
struct cmpclass {
bool operator()(float a, float b){
return a < b;
}
};
//版本2
std::sort(arr,arr + sizeof(arr) / sizeof(decltype(arr[0])), cmpclass());
//版本3
std::sort(arr,arr + sizeof(arr) / sizeof(decltype(arr[0])), [](float a,float b){return a < b;});
其他常用
std::vector<int> num{3,2,1,5};
std::for_each(num.begin(),num.end(),[](int &n){n++;});
std::find_if(num.begin(),num.end(),[](int i){return i%2 == 0;});
lambda 定义
- campture list specifier {body}
- specifier 修饰符 mutable const etc.
- params h和普通函数一致,如果没有的话 可以忽略[]{body}
lamda参数类型可以为auto,而普通函数不行
auto l= [](auto i){return i;};
- return type
auto l= [](int i){return i;};
auto l2 = [](int i) -> int{return i;};
不写返回值类型用auto来推倒
也可以这么写
auto l2 = [](int i) -> auto{return i;};
- campture list
类似lua的upvalue,c++需要显示的捕获
lua中捕获进来后写时复制,python27的lambda捕获,显示写了就是函数参数,否则引用捕获,但是不能lambda body中赋值
[&] 表示通过引用来捕获
[=] 表示通过复制来捕获
[=,&x,y=r+1,this,*this]
int x = 1;
auto lam1 = [&]{x++;};//ok x = 2
auto lam2 = [=]{x++;};//error 复制捕获(只读) 改变个只读变量
//想改的话 通过mutable,由于是复制捕获,改完并不会影响外部x的值
auto lam3 = [=] mutable {x++;};//ok 外部依然是1
int x = 1,y=1;
auto l1 = [x,&y] {y = x + 1;}; //x复制捕获,y引用捕获 外部y = 2
auto l2 = [x=x+1,&y] {y = x + 1;}; //
auto l3 = [obj = std::move(obj)] { //... }
this捕获
- [this] 引用捕获 [*this] 复制捕获
- [] 默认引用捕获
struct S {
void f(int i);
int m_i;
}
//引用捕获
void S::f(int i){
[this](){m_i + 1;}();
}
//复制捕获
void S::f(int i){
//调用复制构造函数了 一个新的对象生成了
[*this](){m_i + 1;}();
}
- 捕获作用域,只能捕获本作用域变量
int x = 1;
int main(){
auto lam = []{x++;}
lam();
//x本作用域捕获不到 这里不是捕获而是引用到 global x
//非局部变量的时候,可以直接访问的,没有发生捕获的动作,捕获作用在局部变量
}
lambda本质
[x,&y](int i) mutable->int{
y = x + i + 1;
return y
}
//等价于编译器生成这样的闭包
class Closure {
mutable int x;
int & y;
Closure(int x,int &y){//...}
int operator()(int i) const {
y = x + 1;
return y;
}
}
//捕获对应着,捕获可以等价于传入Closure(int x,int &y)的参数构造
- std::function
auto f1 = [](int i,int j) {return i+j;};
auto f2 = [](int i,int j) {return i+j;};
auto f3 = [](int i,int j) {return i-j;};
//这3个不同的closure type
std::vector<??> qu;
qu.push_back(f1);
qu.push_back(f2);
qu.push_back(f3);
std::function 是个function warpper,下面可以存储进std::function
- function
- lambda
- functor
- 可以callable对象
//存储function
void print_num(int x){}
std::function<void(int)> f_display = print_num;
f_display(-1);
//存储lambda
std::function<void()> f_display_l = []() { print_num(100); };
f_display_l();
//存储functor
struct cmpclass {
bool operator()(float a, float b){
return a < b;
}
};
std::function<boo(float,float)> f_cmp_functor = cmpclass();
f_cmp_functor(1,2)
auto f1 = [](int i,int j) {return i+j;};
auto f2 = [](int i,int j) {return i+j;};
auto f3 = [](int i,int j) {return i-j;};
//这3个不同的closure type
std::vector<std::function<int(int,int)>> qu;
qu.push_back(f1);
qu.push_back(f2);
qu.push_back(f3);
std::function实现
template<typename RetType>
struct Function{
};
template<typename RetType,typename ArgType>
struct Function<RetType(ArgType)>{
struct CallableBase{
virtual RetType operator()(ArgType arg) = 0;
virtual ~CallableBase(){}
};
//适配各种callable obj
template<typename T>
struct CallableDerive: public CallableBase {
T callable;
CallableDerive(T c):callable(c){}
RetType operator()(ArgType arg) override {
return callable(arg);
}
};
CallableBase* base = nullptr;
template<typename T>
Function(T callable):base(new CallableDerive<T>(callable)){}
~Function(){delete base;}
RetType operator()(ArgType arg){
return (*base)(arg);
}
};
int main(){
//标准库
std::function<int(int)> l = [](int a) {return a+1;};
std::vector<decltype(l)> que;
que.push_back(l);
//自定义的Function
auto l1 = [](int a) {return a;};
Function<int(int)> ml = l1;
std::vector<decltype(ml)> que2;
que2.push_back(ml);
}
结构化绑定
//例子1
//传统写法
std::set<std::string> set;
std::pari<decltype(set),bool> res = set.insert("hello");
if(res.second){}
//结构化绑定
auto [iter,isSucc] = set.insert("hello");
if(isSucc){}
例子2
struct Point {
int x;
int y;
}
Point p{1,2};
//传统写法
auto dis = sqrt(p.x*p.x + p.y*p.y);
p.x = p.x + 1
p.y = p.y + 1
//
auto& [x,y] = p;
auto dis = sqrt(x*x + y*y);
x = x + 1
y = y + 1
cv-auto (& or &&)[id1,id2,id3] = expr
cv-auto (& or &&)[id1,id2,id3]{expr}
cv-auto (& or &&)[id1,id2,id3](expr)
expr可以是个array 或者非union class 类型
- cv-auto (& or &&)[id1,id2,id3] = expr
- auto e = expr //copy
- auto&e = expr //lvalue ref
- auto&&e = expr //rvalue ref
绑定到id1,id2,id3 到e的成员中去 绑定到e 而不是expr上去
auto [x,y] = p;
等价于
auto e = p;
int &x = e.x;
int &y = e.x;
auto& [x,y] = p;
等价于
auto& e = p;
int &x = e.x;
int &y = e.x;
struct Point {
int x;
int y;
Point(const Point&) = delete;
}
auto [x,y] = p; //error
绑定到数组
int a[2] = {1,2};
auto [x,y] = a;
range base for
for (auto itr = smap.begin();iter != smap.end();iter++){}
//c++11
for (const auto& item : smap){
//item.first,item.second
}
//结构化绑定
for(const auto&[first,second] :smap){
}
推倒展开,本质是语法的封装
for(range_declaration : range_expression){
loop_statement
}
auto&& __range = range_expression
auto __begin = begin_expr
auto __end = end_expr
for(;__begin != __end;++__begin){
range_declaration = *__begin
loop_statement
}
枚举类型
unscoped enum
enum Color {red,green,blue}
//可以外部访问 不够安全 没有经过Color
Color r = red;
switch(r){
case red://
case green:
}
//甚至赋值给一个int
int a = red;
enum Color {red,green,blue}
enum OColor {red,green,blue}
//如果两个enum相同的名字,都出现在外部 就会冲突无法编译通过
struct X {
enum Color {red,green,blue};
}
//就算定义在class中,还是会绕过Color的命名空间
int a = X::red
综上 所以unscoped enum的问题
- 名字冲突问题
- 会隐试转换
scoped enum
类型安全的枚举类型
enum class Color {red,blue,green}
Color r = Color::red;
switch(r){
case Color::red://
case Color::green:
}
int n = Color::red;//error 不会隐式转换
int n = static_cast<int>(Color::red); //ok
if初始化语句
//old style
std::set<std::string> set;
auto [iter,succ] = set.insert("hello");
if(succ){
}
//c++ 17
if (auto [iter,succ] = set.insert("hello");succ){
}
if(init-statement;condition){
}
else{
}
等价与
{
init-statement;
if(init-statement;condition){
}
else{
}
}
好处在于ifelse这个block后 就会析构掉变量
RAAI
if(auto lock = get_lock();lock){
}
//if 结束后就立马释放
新的数据类型
std::nullptr_t c;
long long a;
char16_t b;
char32_t d;
auto a = 0b010101;
//自定义字面常量格式15_km
老版中NULL和0 无法区分
问题1
void f(std::string *){}
void f(int){}
f(NULL);//编译不过
问题2
template<class T>
T clone(const T& t){
return t;
}
void g(int*){}
g(NULL); //ok
g(0); //ok
//经过转发后 失去NULL特性
g(clone(NULL)); //error
所以要引入null_ptr 类型为 std::nullptr_t,可以转换成任意类型指针
std::string* a = nullptr;
int* a = nullptr;
还可以这么重载
void g(int*){}
void g(double*){}
void g(std::nullptr_t){}
模板转发也不会丢失类型信息
自定义字面常量
有定义函数operator "" _km(long double);
当使用33_km
就有_km(33) 这样的函数被调用
比如要实现1.0_km + 24.0_m = 1024_m
long double operator "" _km(long double v){
return 1000*v;
}
long double operator "" _m(long double v){
return v;
}
auto a = 1.0_km + 24.0_km;