C++11新特性

智能指针

右值引用、移动构造函数、move函数、完美转发

lambda表达式

一个lambda表达式表示一个可调用的代码单元。我们可以将其理解为一个未命名的内联函数。与任何函数类似,一个lambda具有一个返回类型、一个参数列表和一个函数体。
但与函数不同,lambda可能定义在函数内部。一个lambda表达式具有如下形式
在这里插入图片描述
其中,capture list(捕获列表)是一个lambda所在函数中定义的局部变量的列表(通常为空);return type、parameter list和function body与任何普通函数一样,分别表示返回类型、参数列表和函数体。
但与普通函数不同,lambda必须使用尾置返回来指定返回类型。

我们可以忽略参数列表和返回类型,但必须永远包含捕获列表和函数体

举例:

auto f=[]{return 42;}; //将lambda表达式(匿名函数)类似变量一样赋给f

此例中,我们定义了一个可调用对象f,它不接受参数,返回42。

lambda的调用方式与普通函数的调用方式相同,都是使用调用运算符:

f();//42

尽量“匿名”使用 lambda 表达式
因为 lambda 表达式毕竟不是普通的变量,向上面那样直接赋给f并不是很好的写法, C++ 鼓励程序员尽量“匿名”使用 lambda 表达式
像下面这样:
**开发建议:尽量“匿名”使用 lambda 表达式**
这样写的好处是,lambda 表达式调用完后也就不存在了,最小化了它的影响范围,让代码更加安全

lambda 的变量捕获

在这里插入图片描述
注意点:
1、捕获列表中捕获的变量是lambad表达式所在函数中的局部变量,函数外的全局变量无需捕获可以直接使用

2、值捕获 vs 引用捕获:

  • 使用“[=]”按值捕获的时候,lambda 表达式使用的是变量的独立副本,非常安全。

  • 而使用“[&]”的方式捕获引用就存在风险,当 lambda 表达式调用时可能引用的变量生命周期已经结束了。就会导致出错

开发建议:
建议在使用捕获功能的时候要小心,对于“就地”使用的小 lambda 表达式,可以用“[&]”来减少代码量,保持整洁;
而对于非本地调用、生命周期较长的 lambda 表达式应慎用“[&]”捕获引用
最好是在“[]”里显式写出变量列表,避免捕获不必要的变量。

变量捕获举例:

func(){
    int n = 10;                     // 一个外部变量
    auto f = [=](int x)          // lambda表达式,用“=”值捕获
    {
        cout << x*n << endl;        // 直接操作外部变量
    };
    f(3);                    // 调用lambda表达式
}

注意:如果是引用捕获的话,需要注意在捕获变量生命周期结束之前

其实这种捕获外部变量的行为还有个名字叫闭包
怎么理解闭包?
可以把闭包理解为一个“活的代码块”、“活的函数”。它虽然在出现时被定义,但因为保存了定义时捕获的外部变量,就可以跳离定义点,把这段代码“打包”传递到其他地方去执行、

lambad vs 面向对象|面向过程

和面向过程的比较:
面向过程中,程序流程是按步骤执行的“死程序”,而lambda中是一个个的“活函数”,像做数学题那样逐步计算、推导出结果,有点像下面的这样:

auto a = [](int x)      // a函数执行一个功能
            {...} 
auto b = [](double x)    // b函数执行一个功能
            {...}
auto c = [](string str)  // c函数执行一个功能
            {...}

auto f = [](...)        // f函数执行一个功能
            {...}

return f(a, b, c)            // f调用a/b/c运算得到结果

和面向对象的比较:
你也可以再对比面向对象来理解。在面向对象编程里,程序是由一个个实体对象组成的,对象通信完成任务。而在函数式编程里,程序是由一个个函数组成的,函数互相嵌套、组合、调用完成任务。

auto vs decltype

二者都是类型推断符,让编译器帮我们做类型推断
最大的区别在于:

  • auto a=1+2 是让编译器通过=右边的初始值1+2来推断变量类型
  • decltype(expr)是让编译器通过分析括号中的表达式expr,取得表达式的类型作为变量类型
  • auto 在书写格式上比 decltype 简单,但是它的推导规则复杂,有时候会改变表达式的原始类型;
  • 而 decltype 比较纯粹,它一般会坚持保留原始表达式的类型,让推导的结果更加原汁原味

二者推导规则不同在于对引用的处理,具体如下:
decltype 会保留引用类型,而 auto 会抛弃引用类型,直接推导出它的原始类型。请看下面的例子:

#include <iostream>
using namespace std;
int main() {
    int n = 10;
    int &r1 = n;
    //auto推导
    auto r2 = r1;
    r2 = 20;
    cout << n << ", " << r1 << ", " << r2 << endl;
    //decltype推导
    decltype(r1) r3 = n;
    r3 = 99;
    cout << n << ", " << r1 << ", " << r3 << endl;
    return 0;
}
运行结果:
10, 10, 20
99, 99, 99

从运行结果可以发现,给 r2 赋值并没有改变 n 的值,这说明 r2 没有指向 n,而是自立门户,单独拥有了一块内存,这就证明 r 不再是引用类型,它的引用类型被 auto 抛弃了。

给 r3 赋值,n 的值也跟着改变了,这说明 r3 仍然指向 n,它的引用类型被 decltype 保留了。

using的三种用法

1、导入命名空间
使用C++在写不同的功能模块时,为了防止命名冲突,建议对模块取命名空间,这样在使用时就需要指定是哪个命名空间。

2、指定别名
通常用typedef给一个类型取别名,但是typedef不能为模版取别名
c++11提供 的using ,可以用于模板别名,

template <typename Val>
using str_map_t = std::map<std::string, Val>;

3、使用 using 关键字在派生类中引用基类成员,同时修改访问权限

//基类People
class People {
public:
    void show();
protected:
    char *m_name;
    int m_age;
};

//派生类Student 继承  基类People
class Student : public People {
public:
    void learning();
public:
    using People::m_name;  //将protected改为public
    using People::m_age;  //将protected改为public
    float m_score;
private:
    using People::show;  //将public改为private
};

noexcept

使用noexcept表明函数或操作不会发生异常

好处是可以减少运行时开销,使得编译器有更大的优化空间。
因为当出现异常时,会抛出一些相关的堆栈错误信息, 这里面包含:错误位置, 错误原因, 调用顺序和层级路径等信息;
当使用noexcept声明一个函数不会抛出异常时,编译器就不会去生成这些额外的代码, 减少运行时开销.

initializer_list

当函数的实参数量未知,但是我们知道全部实参的类型相同,那么就可以用initializer_list类型的形参。即initializer_list用于表示某种特定类型的值的数组

initializer_list提供的操作如下:

initializer_list<T> lst; 
//默认初始化;T类型元素的空列表
initializer_list<T> lst{a,b,c...};
//lst的元素数量和初始值一样多;lst的元素是对应初始值的副本
lst2(lst)   
lst2=lst  
//拷贝或赋值一个initializer_list对象不会拷贝列表中的元素;拷贝后,原始列表和副本元素共享
lst.size()  //列表中的元素数量
lst.begin()  //返回指向lst中首元素的指针
lst.end()   //返回指向lst中尾元素下一位置的指针

需要注意的是:initializer_list对象中的元素永远是常量值,我们无法改变initializer_list对象中元素的值。并且,拷贝或赋值一个initializer_list对象不会拷贝列表中的元素,其实只是引用而已,原始列表和副本共享元素。

和使用vector一样,我们也可以使用迭代器访问initializer_list里的元素

void error_msg(initializer_list<string> il)
{
   for(auto beg=il.begin();beg!=il.end();++beg)
      cout<<*beg<<" ";
   cout<<endl;
}

如果想向initializer_list形参中传递一个值的序列,则必须把序列放在一对花括号内:

//expected和actual是string对象
if(expected != actual)
   error_msg({"functionX",expectde,actual});
else
   error_msg({"functionX","okay"});

说了这么多,那initializer_list到底有什么应用呢?

有了initializer_list之后,对于STL的container的初始化就方便多了,比如以前初始化一个vector需要这样:

std::vector v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);

而现在c++11添加了initializer_list后,我们可以这样初始化

std::vector v = { 1, 2, 3, 4 };

tuple

tuple是一个固定大小的不同类型值的集合,是泛化的std::pair。std::tuple理论上可以有无数个任意类型的成员变量,而std::pair只能是2个成员,因此在需要保存3个及以上的数据时就需要使用tuple元组了。

使用:

std::tuple<double, char, std::string> t = std::make_tuple(3.14, 'A', "StoneLiu");
std::cout << "tuple length " << std::tuple_size<decltype(t)>::value;
std::cout << "first: " << std::get<0>(t) << ", two: " << std::get<1>(t) << ", three: " << std::get<2>(t) ;

bitset

bitset是一种类似数组的结构,它的每一个元素只能是0或1,每个元素仅用1bit空间。常用于位运算

定义和初始化:

bitset<n> b;	          //b有n位,每位都为0
bitset<n> b(u);	          //b是unsigned long型u的一个副本
bitset<n> b(s);	          //b是string对象s中含有的位串的副本
bitset<n> b(s, pos, n);	  //b是s中从位置pos开始的n个位的副本

使用:

#include <iostream>
#include <bitset>
using namespace std;

int main() {
    bitset<10> v1;//定义10位的bitset变量
    v1 = 8;//将v1赋值为8
    bitset<10> v2(v1);//初始化,与v1相同
    bitset<10> v3 = v2;//赋值,=v2
    
    //从右往左数,最右边的为第一位,向左依次加1
    v1[1] = 1;//将v1的第一位置为1
    //v1.set(1);

    cout << "v1=" << v1 << endl;//用二进制的形式输出v1
    cout << "v2=" << v2 << endl;
    cout << "v3=" << v3 << endl;
    
    for (int i = (int)(v1.size()) - 1; i >= 0; --i)//一位一位的输出
        cout << v1[i];
    cout << endl;
    
    cout << "any() --- " << v1.any() << endl;
    cout << "count() --- " << v1.count() << endl;
    cout << "flip --- " << v1.flip() << endl;
    cout << "none() --- " << v1.none() << endl;
    //其他函数用法相似
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++11引入了许多新特性,其中包括线程池的实现。在引用中的代码中,ZERO_ThreadPool类封装了线程池的功能。线程池的原理是通过维护一个线程队列和一个任务队列来实现的。 在初始化阶段,通过调用init函数来初始化线程池。该函数会创建指定数量的线程,并将其保存在threads_队列中。如果线程池已经被初始化过,则直接返回false。 在启动线程池后,调用start函数。该函数会循环创建指定数量的线程,并将它们放入threads_队列中。每个线程都会调用run函数来执行任务。 当调用exec函数时,会将任务添加到tasks_队列中。其中,std::bind用于绑定一个成员函数和其参数,以及占位符std::placeholders::_1表示传入的参数。 在waitForAllDone函数中,会判断atomic_是否为0且tasks_是否为空。如果是,则表示所有任务已经执行完毕,线程池可以退出。 线程池的stop函数用于停止线程池的运行。它会遍历threads_队列,并调用每个线程的join函数,等待线程执行完毕后再返回。 以上就是C++11新特性线程池的基本原理。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [基于C++11新特性手写线程池实现](https://blog.csdn.net/m0_70418130/article/details/126805390)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值