【C++】c++11学习-常用特性总结

news2024/9/22 17:32:22

前言 

        由于种种历史原因,C++的C++11版本更新带来了很多有用的东西~,在C++98的基础语法体系之上,来看看C++11新增了哪些实用的特性吧~

 (加把劲~~(๑╹◡╹)ノ""")

目录 

一、列表初始化

1.原始的列表{}初始化

2.内置类型的列表初始化

3.自定义类型的列表初始化

二、推导类型

1.auto

2.typeid().name()

3.decltype

三、范围for

四、final、override关键字

五、智能指针

六、新增容器

七、默认成员函数控制

1.显示缺省函数

2.删除默认成员函数

八、引用

1.左值引用和右值引用

1.1左值和右值

1.2引用交叉使用

2.左值引用无法解决的问题

3.移动拷贝和移动赋值

4.万能引用&完美转发

5.总结

九、可变参数

1.利用模板参数递归推导

 2.利用数组去实例化每一个参数

十、lambda表达式

十一、包装器、绑定

1.包装器

2.绑定

十二、线程库


一、列表初始化

1.原始的列表{}初始化

        说到最原始的列表初始化,就不得不从我们接触到的第一个数组开始说起~

void test1()
{
    // 列表初始化
    int arr[] = {1, 2, 3, 4};
    for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); ++i)
    {
        cout << arr[i] << " ";
    }
    cout << endl;
}

        但是对于自定义类型的话,就不能像上面的数组那样进行连续的赋值:

    vector<int> v = {1, 2, 3};

         那么这样的话,每次对于自定义类型想要一次性连续赋值构造就无法实现,只能自己手动利用循环进行赋值,很不方便。

2.内置类型的列表初始化

        C++11语法对{}的功能进行扩大化,使得{}变的万物皆可。

内置类型:

        进行初始化的时候,均可使用{}。连续赋值对应每个元素之间加上,即可。并且'='可以省略。

    int x = {1};
    int y {2};
    int ar1[]{1, 2, 3};
    int ar2[3]{1};
    int* ar3 = new int[3]{2, 4, 5};

        一般不推荐把'='省略掉。

3.自定义类型的列表初始化

         C++11语法将{}扩大化就是为了让自定义类型能够更好的进行初始化,首先是STL内的容器想要初始化连续的元素值的话:

    vector<int> v = {1, 2, 3};
    unordered_map<string, string> m = {{"language", "语言"}, {"practical", "实用"}};
    for (auto e : v) cout << e << " ";
    cout << "\n" << m["language"] << m["practical"] << endl; 

         同时=也可以类似操作。

    unordered_map<string, string> m = {{"language", "语言"}, {"practical", "实用"}};  // 首先支持initalizer_list构造,其次就是默认类型转换。
    m = {{"玉子", "饼藏"}};
    cout << m["玉子"] << endl;

自定义类型:

        {}首先支持了多参数的默认类型转换

        {}在C++11中可以看做是一个新的类型initializer_list。此类型模板主要有着迭代器的相关操作以及获取其中元素个数的方法size()。 

        库里的容器之所以可以使用{}进行初始化或者=使用列表进行初始化实际上就是重载了参数类型为initializer_list的构造方法或者赋值重载而已,所以如果想让自己定义的类型可以实现列表初始化就重载initializer_list的相关方法即可。

initializer_list类型文档:

initializer_list - C++ Reference (cplusplus.com)

        测试{}类型:

    auto e  = {1, 2};
    cout << typeid(e).name() << endl;

        以下面这个自定义程序为例,测试默认类型转换和进行 initializer_list构造方法编写,使其列表可以一次性初始化多个值。

#include <initializer_list>

class Student
{
public:
    Student(string name, size_t id)
    :_name(name), _id(id)
    {}
    const string& PutName()
    {
        return _name;
    }
    size_t PutId()
    {
        return _id;
    }
private:
    string _name;
    size_t _id;
};

class StudentManagement
{
public:
    // 实现列表初始化
    StudentManagement(const initializer_list<Student>& l)
    :_size(l.size())
    {
        for (auto& e : l)  // 底层有迭代器 - 可以使用范围for
        {
            _v.push_back(e);
        }
    }

    void Printlist()  // 打印测试
    {
        for (auto& e : _v)
        {
            cout << e.PutName() << ":" << e.PutId() << endl;
        }
    }
private:
    vector<Student> _v;
    size_t _size;
};

void test2()
{
    // 测试自定义类型列表初始化
    Student s = {"张三", 1};  // 默认类型转换
    cout << s.PutName() << ":" << s.PutId() << endl << endl;
    StudentManagement ss{{"李四", 2}, {"王五", 3}, {"小明", 4}, {"小美", 5}};  // 列表初始化对自定义类型进行多个对象初始化:默认类型转换 + 列表初始化构造函数
    ss.Printlist();
}

注:里面所用的范围for在下面会细讲哦~ 

二、推导类型

1.auto

        在类型很长的情况下,我们书写代码会变得非常的不方便,比如如下的一段代码就可体现出来:

void test3()
{
    // 类型推导
    unordered_map<string, string> m{{"我不高兴", "不愉快です"}, {"我很好奇!", "わたし、気になります!"}};
    unordered_map<string, string>::iterator ib = m.begin();
    while (ib != m.end())
    {
        cout << ib->first << ":" << ib->second << endl;
        ++ib;
    }
}

        可以看到上述代码在写迭代器的变量的时候很长,不是很方便,但是auto关键字可以自动推导类型方便省事:

    // unordered_map<string, string>::iterator ib = m.begin();
    auto ib = m.begin();

 

        可以看到此时就可以省去很多书写的时间啦~

auto:

        auto后面的变量必须初始化,此时auto就会变为此变量初始化对应的类型。

2.typeid().name()

        和auto不同,如果单纯想要知道一个对象的类型的话,这种或许可以对你的胃口。

头文件:#include <typeinfo>

typeid(对象/类型).name() ->string 

注意:返回是一个字符串,不能像auto那样当做变量的类型。

    cout << typeid(ib).name() << endl;  // 之前迭代器的对象

  

3.decltype

        还有一种,根据表达式的实际类型推导出定义变量时候的类型。注意是定义变量的时候的类型,也就是说可以利用此来定义变量。

decltype(表达式) -> 根据表达式推导出来的类型。

表达式:可以是一个元素、函数、函数返回值等等。不会去执行,而是去推演。

    int x = 2;
    decltype(x) y;  // 定义对象
    y = x;
    cout << y << endl;
    double z = 2.1;
    cout << typeid(decltype(x * y)).name() << endl;  // int int
    cout << typeid(decltype(x * z)).name() << endl;  // int double

  

三、范围for

使用格式:

        for(auto 变量名 : 可迭代对象)

        {

                //..... 

        }

        自动从可迭代对象中取每个元素,自动往后迭代。

        对于auto 变量名来说,可以auto& 变量名使用改变可迭代对象里元素的值(如果可以修改的话),当然也可以减少拷贝。

(底层通过迭代器调用实现。只要对象类型实现了迭代器就可以进行范围for)

    vector<int> vv = {1, 2, 3, 4, 5, 6};
    for (auto& e : vv)
    {
        cout << e << " ";
        e++;  // 加上引用修改里面的值
    }
    cout << endl;  
    for (auto e : vv)
    {
        cout << e << " ";  // 正常范围for遍历,并且验证上面是否修改
    }
    cout << endl;

四、final、override关键字

final:

        1.在定义类的后面即class/struct 类名 添加关键字final表示此类为最终类,无法被继承。

        2.在定义虚函数的后面即virtual 虚函数名() final表示该虚函数不可被重写。

override:

        检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。同样的跟在虚函数后面。

        验证final: 

         验证override:标记了覆盖,必须覆盖基类的虚函数。注意是虚函数

五、智能指针

        智能指针的出现能够让我们从前申请的资源必须手动释放变成自动托管生命周期结束自动释放。当然也是C++填没有垃圾回收的坑,并且随着异常的出现,也就更加需要智能指针的存在。

        智能指针在另一篇文章详细介绍哦~ 未完待续......

六、新增容器

        在C++11中,STL增加了array、forward_list以及unordered系列的容器。其中unordered是利用哈希表数据结构实现的,作用显著,其他的相对起来比较鸡肋。

array

        -可以把这个容器理解为静态的顺序表。

        -支持 class T size_t N   []  迭代器
        -C++11支持array的初衷--想要替代C语言的数组  -- 越界检查、容器化
        -C语言的数组,越界读基本检查不出来,越界写时抽查。但是array就不一样了,都可以被检查出来。
        -但是用的很少 -- 一方面用C的数组用惯了,另一方面用array不如vector->vector + resize  另外array可能导致栈溢出。是在栈上开辟空间的哦。

        相关文档:array - C++ Reference (cplusplus.com)

forward_list 单链表

        -优势:比其list(双向链表)空间节省那么一丁点
        -但是给的接口比较怪:erase删除下一个,insert_after  插入后面

        相关文档:forward_list - C++ Reference (cplusplus.com)

        unordered系列也是在另一篇文章详细介绍哦~未完待续......

        另外,容器里面也新增了一些方法:

容器里方法的变化:
    1.都支持了initializer_list构造,用来支持列表初始化。
    2.比较鸡肋的一些接口:cbegin、cend系列(不够明确?)
    *3.移动构造和移动赋值。set(set&& x)  set& operator = (set&& x);
    *4.右值引用参数的插入。insert、emplace    

    // 3 - 4提高效率(右值引用下面会进行详细介绍)

七、默认成员函数控制

1.显示缺省函数

        在C++11中,可以在默认函数定义或者声明时加上=default,从而显式的指示编译器生成该函数的默认版本,用=default修饰的函数称为显式缺省函数。

         常用于显示写构造函数后还需要一个默认版本的,此时就可以如上进行操作。

2.删除默认成员函数

        如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且不给定义,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数

         注意:避免删除函数和explicit一起使(explicit是防止函数进行隐式类型转换)。

八、引用

1.左值引用和右值引用

        C++98定义了引用。即引用就是给一个变量取别名,然后两个变量共享一个地址空间,比如swap函数的实现。详细介绍可参考【C++】基础学习笔记_柒海啦的博客-CSDN博客。

        其实在C语言中就区分了左值和右值。在C++11中加强了这种区分,并且也增加了一种引用,区别于C++98的引用,叫做右值引用。C++98的引用为左值引用。在了解引用之前,首先了解一下左值和右值有着如何区别?

1.1左值和右值

左值

        1.普通类型的变量,有名字,可以取地址,认为其为左值。

        2.const修饰的变量,也可以去地址,认为其为左值。

        3.左值可以出现在等号的左边或者右边。

        左值引用:类型& 别名 = 左值;

右值

        1.常量,无法获取地址。

        2.产生的临时对象,将亡值(表达式产生的中间结果,函数按值返回)。

        3.只能出现在等号的右边。

        右值引用:类型&& 别名 = 右值; (右值被引用后也有变量名,可以被取地址和赋值,所以就变成了左值)

        以下代码验证左值和右值:

int add(int x, int y)
{
    return x + y;  // 传值返回
}

void test5()
{
    // 引用

    // 左值
    int a = 10;
    int& _a = a;
    cout << &a << endl;
    const int b = 12;
    cout <<  &b << endl;
    a = b;  // 可以出现等号的左边
    // 右值
    int&& d = 10;  // 常量 - 纯右值
    int&& e = a + b;  // 表达式相加返回的是一个临时对象
    int&& f = add(1, 2);  // 将亡值或者也是一个临时对象
    int& g = f;  // f此时也是一个左值
}

        实际上,临时对象是临时的,所以也可以认为其是一个将亡值。

1.2引用交叉使用

         那么在C++11之前就无法引用右值吗?不是的。对于左值引用来说,加上const关键字修饰就可以引用右值了。表示其对象不可修改。

    const int& a = 10;  // const修饰后的左值引用可以引用右值
    const int& b = add(1, 2);  // 同上

        既然左值引用可以引用右值,那么右值引用可以引用左值吗?可以的。对于左值使用库中的std::move(左值)转化为右值就可以给右值引用了。只不过需要注意move的作用是转移资源,实际上不做任何事情,只是可被右值引用而已,左值没变。但是可以实现转移语义(如果实现了移动构造就需要注意了,下面会讲)。

    int a = 10;
    int&& c = move(a);
    cout << c << endl;
    cout << a << endl;

         那么既然右值可以被左值引用进行引用,那么我们为什么需要右值引用呢?我们利用下面这个例子进行模拟需要用右值引用的场景:

2.左值引用无法解决的问题

class Student
{
public:
    Student(string name, size_t id)
    :_name(name), _id(id)
    {}
    Student() = default;  // 生成默认构造函数
    // 拷贝构造
    Student(const Student& stu)
    {
        _name = stu._name;  // 调用string类的赋值重载
        _id = stu._id;
        cout << "Student发生了拷贝构造" << endl;
    }
    const string& PutName()
    {
        return _name;
    }
    size_t PutId()
    {
        return _id;
    }

private:
    string _name;
    size_t _id;
};

class StudentManagement
{
public:
    // 实现列表初始化
    StudentManagement(const initializer_list<Student>& l)
    :_size(l.size())
    {
        for (auto& e : l)  // 底层有迭代器 - 可以使用范围for
        {
            _v.push_back(e);
        }
    }

    void Printlist()  // 打印测试
    {
        for (auto& e : _v)
        {
            cout << e.PutName() << ":" << e.PutId() << endl;
        }
    }

    // 假设上述程序实现id去重操作
    Student IdOfStudent(size_t id)  // 根据id返回一个新学生对象(不是vector内的),不存在就设置一个空对象
    {
        for(auto& e : _v)
        {
            if (e.PutId() == id) return e;  // 传值返回
        }
        return Student("null", id);  // 不存在返回空的新对象
    }
private:
    vector<Student> _v;
    size_t _size;
};

        上述代码是上面的一个例子,现在新增了一个IdOfStudent函数,意思是如果学生管理库中存在id一样的学生对象,返回其的拷贝对象,如果不存在就返回一个空名字的新对象。

         由于是传值返回,我们显示写了元素类型的拷贝函数,打印出对应调用其信息,利用如下测试代码在debug模式下运行(为了减少迭代器的优化,查看最原始的效果,我在Linux下的g++后加-fno-elide-constructors进行取消优化),查看代码及其结果:

    StudentManagement sm = {{"张三", 1}, {"李四", 2}, {"王五", 3}};
    cout << endl << endl;
    Student s1 = sm.IdOfStudent(2);  // 2次拷贝构造:vector中的对象-临时对象-外面的对象
    cout << endl;
    Student s2 = sm.IdOfStudent(4);  // 2次拷贝构造:匿名对象-临时对象-外面的对象

(注意初始化的时候拷贝信息没有截上,无用)

 

        针对上面的情况,可以简单的用如下图进行展示,就可以看的出来问题了:

        第二种情况同理。可以发现,如果是传值返回的话,发生这种情况每次对象转移资源的时候就会拷贝构造一次,对象里面的值比如string也就是进行一次深拷贝。即使编译器优化也会发生拷贝构造,而发生拷贝构造后,原本的空间又被释放掉了。这不仅仅是对空间的浪费,程序效率也会十分低下。赋值同理。

        针对此种情况,以前的解决方法一般就是设置一个输出型参数,这样就可以借助引用不会发生多余的空间浪费了。C++11引入了两个默认成员函数,移动拷贝和移动赋值。

3.移动拷贝和移动赋值

移动拷贝

        类名(类名&& 名){ //转移资源即可 }

注意:

        1.对进行深拷贝的对象有效,此时是转移资源,而不是重新进行深拷贝。(上面的例子只是举例,实际上上面对象string类型官方已经实现了移动赋值和移动拷贝,这里只是模拟演示)

        2.移动拷贝如果需要默认生成,需要满足没有实现析构、拷贝构造、拷贝赋值中的其中一个。默认移动拷贝就会做 内置类型值拷贝,自定义成员有移动构造调用移动,没有就调用拷贝构造。

(实际上如果存在需要进行深拷贝,上述的成员函数一定都要实现的,原因就是深拷贝)

移动赋值

        类名& operator=(类名&& 名){  // 转移资源即可 }

注意:

        1.同上类似。

        2.移动赋值如果需要默认生成,需要需要满足没有实现析构、拷贝构造、拷贝赋值中的其中一个。内置值拷贝,自定义类型有实现移动赋值就调用,没有就调用拷贝赋值。

(在大多数条件下,如果出现了深拷贝,都不会让编译器自己默认生成的)

        利用上面的知识我们对上面的程序进行改造一下(假设string类没有实现移动构造和赋值)

    // 移动构造
    Student(Student&& stu)  // 右值识别进入函数
    {
        stu._name.swap(_name);  // 转移资源
        _id = stu._id;
        cout << "Student发生了移动构造" << endl;
    }
    // 移动赋值
    Student& operator=(Student&& stu)
    {
        stu._name.swap(_name);  // 转移资源
        _id = stu._id;
        cout << "Student发生了移动赋值" << endl;
        return *this;
    }

        此时,上面的测试程序中,第一个如果学生管理里有的话首先是进行拷贝构造,因为e为引用,不会被识别为将亡值,自然进入左值引用的区域,然后在临时对象给外面的对象的时候,临时对象被识别为右值,发生移动构造。第二个同理,只不过第一个也是移动构造(匿名对象-临时对象-右值),此时在加一个赋值同理:

    Student s1 = sm.IdOfStudent(2);  // 拷贝构造+移动构造:vector中的对象-临时对象-外面的对象
    cout << endl;
    Student s2 = sm.IdOfStudent(4);  // 2次移动构造:匿名对象-临时对象-外面的对象
    cout << endl;
    Student s4;
    s4 = Student("xxx", 0);  // 赋值 - 移动赋值

 

        注意右值引用并不是像左值引用那样直接触发(也就是作为返回值的出现,作为返回值的话,到外面去还是被识别为左值,不会产生任何影响,该拷贝还是拷贝) 

        右值可以大胆的进行交换资源,因为右值是一个将亡值,但是左值就不行了。

4.万能引用&完美转发

        在模板中,如果将模板类型定义为T&& 此时T&&就为万能引用,也就是说左值可以传入,右值也可以传入(实际上const T&类似,右值也可以传入)。但是呢,这样的话会发生引用折叠。也就是说此时此万能引用类型的对象的属性会被统统识别为左值(其实只要是引用对象名都会被识别为左值),那么我们如果想要将右值的类型延续下去该怎么办呢?使用完美转发就可以了。

万能引用:

ed:

        template<class T>

        test(T&& t) ->t就是一个万能引用

        注意:

                此时t传递下去被识别为左值

完美转发:

        std::forward<T>(万能引用对象); 

        此时返回的对象就是其原本的左值或者右值属性。

        以下面的代码作为例子进行测试:

// 重载不同类型参数的相同函数名
void Func(int& x)
{
    cout << "左值" << endl;
}

void Func(int&& x)
{
    cout << "右值" << endl;
}

void Func(const int& x)
{
    cout << "const左值" << endl;
}

void Func(const int&& x)
{
    cout << "const右值" << endl;
}

template<class T>
void test(T&& t)  // 万能引用
{
    Func(forward<T>(t));  // 完美转发
}
void test6()
{// 完美转发
    test(1);  // right

    int x = 1;
    test(x);  // left
    test(move(x));  // right

    const int y = 2;
    test(y);  // const left
    test(move(y));  // const right
}

  

5.总结

        综上,C++11新增的右值引用的作用如下:

1.能够实现移动语义,即移动拷贝和移动赋值。增加效率。

2.给右值取名字。

3.实现完美转发。

九、可变参数

        早在C语言的printf函数内,我们就见到了可变参数。那么我们如果想要自己定义一个可变参数怎么办?这就要用到可变参数包了:

template<class ...Args>

void test(Args... args){} 

        此时向test函数传参就是0到n个参数了。底层是通过数组进行接收的。

        sizeof...(args) 可以计算可变参数包大小。

        要使用此函数参数包,args... 进行使用。

        那么现在如何拿到对应的参数呢?直接拿是不行的哦~

1.利用模板参数递归推导

void test()
{
    cout << endl;
}

template<class T, class ...Args>
void test(const T& val, Args... args)
{
    cout << val << ":" << sizeof...(args) << endl;
    test(args...);
}
void test7()
{
    test(1, 'a', 'b', "ds", 343);
    test('A', 2, 'c');
}

 2.利用数组去实例化每一个参数

template<class T>
int PrintArgs(const T& val)
{
    cout << val << endl;
    return 0;
}

template<class ...Args>
void test(Args... args)
{
    int arr[] = {PrintArgs(args)...};
    cout << endl;
}

        另外,可变参数在容器新加emplace即可变函数包有些作用。 

十、lambda表达式

        另外C++11引进了一个匿名函数。匿名函数顾名思义,也是能够像函数使用的一个对象。只不过此对象没有特定的类型,所以一般只能auto或者下面的包装器进行接收。

lambda表达式的使用:匿名函数

        使用格式

                [捕捉列表](参数列表)mutable->返回值类型 {函数体实现};

                 (红色的是必须要写的,黑色可以不写)

        详细介绍

                1.捕捉列表:能够捕捉当前作用域中的变量提供给lambda函数使用

                        (假设x和y为当前作用域的两个变量)

                        [x, y]  捕捉x和y,默认为const拷贝。

                        [x, y]()mutable 捕捉x和y,值拷贝,无const属性。

                        [&x, &y] 引用捕捉x和y。

                        [=] 传值捕捉当前作用域所有变量,默认为const类型。

                        [&] 引用传递当前作用域所有的变量。

                        注意:上述的每个使用可以混合使用,只不过满足一个前提:引用传递不能和引用传递一起,传值捕捉不能和传值捕捉一起。(相同类型捕捉不可混在一起)

                2.参数列表:传给此lambda表达式的参数。像正常函数那样使用即可。捕捉列表捕捉了就可以不用传参了。

                3.mutable:捕捉列表传值捕捉默认const,加上此关键字是其const属性消除。

                4.->返回值类型:和函数类似。一般不用写,会自行推导。

                5.函数体实现:函数体的实现,不限制行数。

        代码演示上述lambda表达式-匿名函数的功能。

        捕捉列表:

    int x = 10, y = 2;
    auto func1 = [x, y](){ return x + y; };
    cout << func1() << endl;
    cout << endl;
    // 默认const 使用关键字mutable,去掉const
    auto func2 = [x, y]()mutable{ y -= 6; return x + y; };
    cout << func2() << endl;
    cout << y << endl;  // 只是传值返回
    cout << endl;
    // 引用捕捉
    auto func3 = [&x, &y](){ int tmp = x; x = y; y = tmp; };
    cout << "x=" << x << " y=" << y << endl;
    func3();
    cout << "x=" << x << " y=" << y << endl;
    cout << endl;
    // 混合使用
    int z = 8;
    auto func4 = [&x, &y, z](){
        x = 10;
        y = 2;
        cout << z << endl;
    };
    func4();
    cout << "x=" << x << " y=" << y << endl;
    auto func5 = [=, &x](){  // 符号=先 x引用捕捉,其余传值捕捉
        x = 2;
        cout << y << " " << z << endl;
    };
    func5();
    cout << "x=" << x << endl;
    auto func6 = [&, x](){ // 符号在前 x传值捕捉,其余引用捕捉
        cout << x << endl;
        y = z = 0;
    };
    cout << "y=" << y << " z=" << z << endl;
    cout << endl;

         参数列表:

    // 参数列表
    auto func7 = [](int& x, int& y){
        int tmp = x;
        x = y;
        y = tmp;
    };
    x = 4;
    cout << "x=" << x << " y=" << y << endl;
    func7(x, y);
    cout << "x=" << x << " y=" << y << endl;
    cout << endl;

    

十一、包装器、绑定

1.包装器

        学到目前的程度,我们所能了解的C++函数对象有哪几种呢?函数指针,仿函数对象、lambda表达式对象

        那么我们能否将这些函数对象统一包装成一份去供我们使用呢?C++11提供了包装器可以让我们进行统一的去包装。

包装器:

        所需头文件:functional

        类型展示:

                    template<class Ret, class... Args>
                    class function<Ret(Args...)>

                Ret-返回类型 Args... 参数包

        使用:

                function<返回值类型(参数包)> 对象名 = 函数指针/仿函数对象/lambda表达式/成员函数(成员函数(包括静态)-&类名::成员函数指针)

                注意:如果调用成员函数(非静态),必须要传一个对象 返回类型(类名,参数),因为成员函数存在一个隐藏的this指针哦~。之后调用的时候也就需要传入此类的一个对象。

        相关文档:function - C++ Reference (cplusplus.com)

        以下代码简单展示包装器的使用:

void func1()
{
    cout << "わたし、気になります!" << endl;  // 我很好奇
}

struct func2
{
    void operator()()
    {
        cout << "不愉快です" << endl;  // 我不高兴
    }
};

class test
{
public:
    int fun4(int a, int b)
    {
        return a + b;
    }

    static int fun5(int a, int b)
    {
        return a + b;   
    }
};

void test9()
{
    // 包装器的使用
    function<void()> Func1 = func1;  // 函数指针
    function<void()> Func2 = func2();  // 仿函数对象 - 此处为匿名对象
    function<void(string, string)> Func3 = [](const string& x = "玉子", const string& y = "市场"){  // 注意虽然给了缺省参数,但是包装器上层包装必须要传参的哦~
        cout << x + y << endl;
    };  // lambda表达式
    function<int(test, int, int)> Func4 = &test::fun4;  // 成员函数
    function<int(int, int)> Func5 = &test::fun5;  // 静态成员函数,不存在this指针。
    Func1();
    Func2();
    Func3("玉子", "市场");
    cout << Func4(test(), 1, 1) << endl;  // 别忘了传入对象
    cout << Func5(2, 2) << endl;
}

 

2.绑定

绑定:

        头文件:functional

        类型展示:

            template<class Fn, class... Args>
            bind(Fn&& fn, Args&&... args);  - 适配器 - 对参数的适配、调整参数

         传参:placeholder

                 fn首先为函数对象,函数对象可以是函数指针,lambda表达式,仿函数对象,成员函数。如上。绑定直接传参即可,无需进行显示传模板参数。

                args placeholder-占位符。在此命名空间里_1, _2, _3....分别对应传入之后实际调用的第一个参数,第二个参数..... 可以实现调整参数顺序等操作。

                返回的也是bind相关的函数对象,function可以接收。和function联合一起可以做到类型统一等操作。


        相关文档:bind - C++ Reference (cplusplus.com)

         下面利用简单代码对上述功能进行展示:

class test
{
public:
    int fun4(int a, int b)
    {
        return a + b;
    }

    static int fun5(int a, int b)
    {
        return a - b;   
    }
};
void test9()
{
    //......
    auto t1 = bind(func1);  // 此时返回的也是一个函数对象
    t1();
    cout << typeid(t1).name() << endl;
    auto t2 = bind([](int i){cout << i << " 个" << endl;}, placeholders::_1);  // 此时lambda匿名对象有参数,那么就要用到placeholders中的占位了。
    t2(2);
    // 我们想统一test::func4 和 func5 的function怎么办 - 原本是:<int(int, int)> <int(test, int, int)>
    function<int(int, int)> Func6 = bind(&test::fun4, test(), placeholders::_1, placeholders::_2);  // 在bind中首先传参,这样就能保证function一致了
    function<int(int, int)> Func7 = bind(&test::fun5, placeholders::_1, placeholders::_2);
    // 类型统一很有意义,可以看下面的一段代码
    unordered_map<char, function<int(int, int)>> m;
    m['+'] = Func6;
    m['-'] = Func7;
    char flag;
    int a, b;
    while(cin >> flag)
    {
        cin >> a >> b;
        cout << m[flag](a, b) << endl;
    }
}

  

十二、线程库

        在另一篇文章进行详细介绍,未完待续......

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/32885.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Mysql8.x版本主从加读写分离(二) mysql8.x读写分离

Mysql8.x版本主从加读写分离&#xff08;一&#xff09; mysql8.x主从_争取不加班&#xff01;的博客-CSDN博客 Mycata需要使用jdk 单独一台服务器部署的mycat 192.168.11.143 手动上传jdk的包 tar zxvf jdk-8u121-linux-x64.tar.gz -C /usr/local/ 解压 cd /usr/local…

c++ - 第14节 - c++中的多态

1. 多态的概念 多态的概念&#xff1a;通俗来说&#xff0c;就是多种形态&#xff0c;具体点就是去完成某个行为&#xff0c;当不同的对象去完成时会产生出不同的状态。举个栗子&#xff1a;比如买票这个行为&#xff0c;当普通人买票时&#xff0c;是全价买票&#xff1b;学生…

如何运行黑马程序员redis项目黑马点评(hm-dianping)、常见报错解决与部分接口的测试方法

文章目录一、相关链接二、下载代码方法一&#xff1a;使用git clone方法二&#xff1a;直接下载程序zip压缩包三、如何运行这份代码运行sql文件1、先新建数据库hmdp2、导入项目中的hmdp.sql文件修改application.yaml配置文件配置Mysql1、配置驱动2、配置url&#xff08;这个不一…

Allwinner T3 汽车级处理器为工业级 SoM 提供动力

Allwinner T3 是一款四核 Cortex-A7 汽车级处理器&#xff0c;支持 -40C 至 85C 的宽工业温度范围。对比了全志T3的规格后&#xff0c;我觉得和全志A40i差不多&#xff0c;因为全志有不同的事业部&#xff0c;T系列是面向车规级市场&#xff0c;而A系列一直以来都是面向平板市场…

python之Matplotlib

1.数据可视化是什么? 数据可视化是将数据转换为图或表等信息图像,以一种更直观的方式展示和呈现数据.可视化,是通过图形化的手段进行有效地表达,准确高效,简洁全面地传递某种信息,甚至帮助我们发现某种规律和特征,挖掘数据背后的价值. 2. Matplotlib是什么? matplotlib是一…

教程:如何将一首歌生成一个二维码?

大雨打在树叶上的声音、烟花绽放的声音、邻居家的狗叫声、海浪和沙滩的决斗声、冬天的风掀翻路人衣服的声音、小孩练琴的声音、啤酒冒泡的声音…… 哦&#xff0c;还有你唱歌的声音&#xff0c;这些都可以做成一个二维码。扫码后就可以听到。 如今&#xff0c;声音二维码已经…

华为云GaussDB打造金融行业坚实数据底座,共创数字金融新未来

近期&#xff0c;由北京金融信息化研究所主办的首届中国金融业数据库技术大会在京顺利举行&#xff0c;大会邀请了金融主管单位领导、金融机构高层、以及数据库企业代表和众多数据库领域专家&#xff0c;共同畅谈金融行业数据库应用的创新发展和实践成果。华为云数据库服务产品…

为什么重写 equals 还要重写 hashCode 方法?

关于equals与hashCode关系的描述 我们可以先来看一下这个定理 &#xff08;1&#xff09;如果两个对象的 hashCode 值相等的情况下&#xff0c;对象的内容值不一定相等&#xff08;hash碰撞问题&#xff09; &#xff08;2&#xff09;如果使用 equals 方法比较两个对象内容值…

世界杯“引爆”东南亚电商狂潮,电商人如何选品和营销?

世界杯&#xff0c;作为最受关注的国际赛事之一&#xff0c;2022年的世界杯已经在卡塔尔拉开帷幕&#xff0c;中国元素无处不在&#xff0c;为跨境电商行业带来很多新机遇。对于跨境电商来说&#xff0c;更是要牢牢抓住这场全球赛事的商机&#xff01;那么跨境电商人应该如何紧…

全面焕新|详解 Grafana v9.0.x 新增功能特性

Grafana 9.0 是一个提升 Grafana 易用性的版本&#xff0c;通过全新的和改进的操作界面来获取数据&#xff0c;以及默认的 Grafana 警报体验。 Grafana 的一个主要使命是让可观测性、数据可视化和分析更容易的惠及到每个人。对于像 Prometheus 和 Loki 这样的流行数据源&#…

基于Jeecgboot前后端分离的ERP系统开发代码生成(四)

今天主要做一个采购入库单的功能 这个是主从表&#xff0c;所以也是通过online表单设计完成基本功能 1、采购入库单 按照上面生成代码&#xff0c;基本的录入编辑等功能就可以了&#xff0c;当然一些细节需要后续完善 选择商品 2、审核通过 对于库存的更新&#xff0c;需要进行…

跨境电商独立站站群模式

首先我们来了解一下什么是独立站组模式&#xff1f;独立站群模式是指通过建站工具快速建立多个网站。每个网站都有独立的域名&#xff0c;每个网站只存放某个垂直领域的产品&#xff0c;包装成专业的工厂和卖家。每个网站的产品详情和公司实力展示尽量完美(不是市面上的复制品)…

Spring——IOC容器启动及Bean生成流程

IOC容器启动及Bean生成流程一、容器启动IOC启动流程重点二、扫描并注册BeanDefination加载并过滤资源注册BeanDefination三、BeanFactory后置处理四、注册Bean后置处理器五、遍历BeanDefination&#xff0c;实例化单例BeanpreInstantiateSingletonsdoGetBean&#xff08;我们只…

idea配置tomcat,解决控制台乱码问题

问题描述&#xff1a; 配置tomcat&#xff0c;解决idea运行tomcat8.5.27版本控制台输出乱码。 版本&#xff1a; jdk1.8.0_172 IntelliJ IDEA 2019.3 3 (Ultimate Edition) apache-tomcat-8.5.27 解决步骤&#xff1a; 1、打开目录 apache-tomcat-8.5.27\conf\ 目录下 logging…

编译原理—运行环境、局部存储分配、活动记录、全局栈式存储分配、非局部名字的访问、参数传递

编译原理—运行环境、局部存储分配、全局栈式存储分配、非局部名字的访问、参数传递#pic_center 65%x55%1. 运行环境1.1存储组织与分配1.2运行时内存划分2.活动记录2.1活动记录的内容2.2活动记录内容的存取3.静态存储分配3.1FORTRAN静态存储分配4. 动态存储分配4.1 栈式分配下的…

Java反射(Reflex)机制

反射概述 Reflection(反射)是Java被视为动态语言的关键&#xff0c;反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息&#xff0c;并能直接操作任意对象的内部属性及方法。 加载完类之后&#xff0c;在堆内存的方法区中就产生了一个Class类型的对象&#x…

【linux】linux实操篇之软件包管理

前言 关于这一快软件包管理知识点不多&#xff0c;我们主要认识一下rpm和yum这两个软件包管理工具&#xff0c;主要还是yum的使用&#xff01; rpm 包的管理 一种用于互联网下载包的打包及安装工具&#xff0c;它包含在某些 Linux 分发版中。它生成具有 .rpm 扩展名的文件。…

QQ plot 的解读

QQ plot全称是Quantile-Quantile Plot&#xff0c;分位数-分位数图是通过比较两个概率分布的分位数对这两个概率分布进行比较的概率图方法。 这个图形的形式非常简单&#xff0c;有点类似RNA-seq中评价两个样本相关性的散点图&#xff08;图1&#xff09;。这类图形为什么那么…

Java小技能:多级菜单排序并返回树结构菜单列表

文章目录 引言I 生成树形结构菜单列表1.1 获取全部菜单1.2 获取一级菜单,递归获取子节点。1.3 实体1.4 DtoII 常见问题2.1 no instance(s) of type variable(s) R exist so that void conforms to R,2.2 集合filter过滤Integer数值为空问题解决方案引言 需求: 服务商角色配置…

面试官:说说你了解的分布式 ID 生成方案

为什么需要分布式 ID 对于单体系统来说&#xff0c;主键 ID 常用主键自动的方式进行设置。这种 ID 生成方法在单体项目是可行的&#xff0c;但是对于分布式系统&#xff0c;分库分表之后就不适应了。比如订单表数据量太大了&#xff0c;分成了多个库&#xff0c;如果还采用数据…