c++新特性

C++ 11 新特性

新类型

C++11 中新增long long 类型和unsigned long long ,以支持64位的整型

#include <iostream>

int main() 
{
    long long a = 3LL;
    unsigned long long b = 4ULL;

    printf("long size = %d\n", sizeof(long));  // 4
    printf("long long size = %d\n", sizeof(long long));  // 8
    printf("unsigned long long size = %d\n", sizeof(unsigned long long));  // 8
}


新增类型char16_t 和char32_t,以支持16位和32位的字符表示,均为无符号数,可能随系统不同而不同

#include <iostream>

int main()
{
    char16_t c = u'A'; // 16位Unicode字符
    char32_t c1 = U'A'; // 32位Unicode字符

    printf("char16_t size = %d\n", sizeof(c)); // 2
    printf("char32_t size = %d\n", sizeof(c1)); // 4

    return 0;
}

新增原始字符串

#include <iostream>
#include <string>

int main()
{
    char str1[] = "helloWorld\n";
    char str2[] = R"(helloWorld\n)"; // 所见即所得

    printf("str1 = %s", str1);
    printf("str2 = %s", str2);

    return 0;
}

统一的初始化

C++11扩大了用大括号括起的初始化列表的使用范围,使其可用于所有内置类型和用户定义的类型(即类对象)。使用初始化列表时,可添加等号(=),也可不添加。

基本类型的初始化

#include <iostream>

int main()
{
    int x = {5};
    double y {3.14};
    short arr[5] {1, 2, 3, 4, 5};
    char *str = new char[10] {'a', 'b', 'c', 'd'};

    printf("x = %d\n", x);
    printf("y = %f\n", y); 
    printf("arr[0] = %d\n", arr[0]); 
    printf("str = %s\n", str); 
}

类的初始化

#include <iostream>

class MyClass
{
private:
    int length;
    double width;

public:
    MyClass(int l, double w): length(l), width(w) {}
};

int main()
{
    MyClass obj(10, 5.5); // old c++
    MyClass obj1 {100, 20.2}; // c++ 11
    MyClass obj2 = {1000, 30.3}; // c++ 11
}

如果类有将模板std::initializer_list作为参数的构造函数,则只有该构造函数可以使用列表初始化形式。我们在使用vector等STL时候,发现它的初始化列表可以是任意长度。

#include <iostream>
#include <vector>

class MyVector
{
private:
    std::vector<int> vec;

public:
    MyVector(std::initializer_list<int> lst)
    {
        for (const auto&item: lst)
        {
            vec.push_back(item);
        }
    }

    void print()
    {
        for (const auto&item : vec)
        {
            std::cout << item << " ";
        }
    }
};

int main()
{
    MyVector v {1, 2, 3, 4, 5, 6};
    v.print();

    return 0;
}

std::initializer_list除了作为构造函数外,还可以用于函数

#include <iostream>

double sum(std::initializer_list<double> lst)
{
    double sum = 0;
    for (auto it=lst.begin(); it!=lst.end(); ++ it)
    {
        sum += *it;
    }
    return sum;
}

int main()
{

    double res = sum({1, 1, 2, 3, 5, 8, 13});

    printf("res = %lf\n", res);

    return 0;
}

缩窄

初始化列表语法可以防止缩窄,即禁止将数值赋给无法存储它的数值变量

#include <iostream>

int main()
{
    char c = 1.57; // warn
    printf("c = %d\n", c);

    char c1 {1.57}; // error
    printf("c1 = %d\n", c1);

    return 0;
}

声明

auto

C++11提供了多种简化声明的功能,尤其在使用模板时。

以前,关键字auto是一个存储类型说明符,c++11将其用于实现自动类型推断

#include <iostream>

double fm(double, int)
{
    return 1.1;
}

int main()
{
    // auto int i = 10; // 原来的用法 由于只有这一种用法 被c++11将这个关键字拿来用了

    auto i = 10;
    auto pt = &i;
    auto pf = fm;

    char c = 'A';
    printf("type of c = %s\n", typeid(c).name());
    return 0;
}

auto简化模板声明

#include <iostream>

int main()
{
    std::initializer_list<int> lst{1, 2, 3, 4, 5};

    for (std::initializer_list<int>::iterator it = lst.begin(); it != lst.end(); ++ it)
    {
        printf("val = %d\n", *it);
    }

    for (auto it = lst.begin(); it != lst.end(); ++ it)
    {
        printf("val = %d\n", *it);
    }
    return 0;
}

decltype

如果我仅仅想推到类型而不产生变量怎么处理,关键字decltype将变量的类型声明为表达式指定的类型。

double x;
int n;
decltype (x*n) q;
decltype (&x) pd;

这在定义模板时特别有用,因为只有等到模板被实例化时才能确定类型

template <typename T, typename U>
void ef(T t,U u)
{
    decltype(t*u) tu;
    //...
}

nullptr

nullptr是c++11用来表示空指针新引入的常量值,在c++中如果表示空指针语义时建议使用nullptr而不要使用NULL,因为NULL本质上是个int型的0,其实不是个指针

#include <iostream>

void func(void *ptr){
    std::cout << "func ptr" << std::endl;
}

void func(int i){
    std::cout << "func i" << std::endl;
}

int main()
{

    func(NULL);  //  call of overloaded 'func(NULL)' is ambiguous
    func(nullptr);
    return 0;
}

智能指针

如果在程序中使用new从堆中分配内存,等到不再使用时,应使用delete将其释放掉。但这个过程还是挺繁琐,稍不注意就会造成内存泄漏。所以c++11给我们引入了智能指针auto_ptr,来帮助我们自动完成这个过程。基于程序员的编程体验和BOOST库提供的解决方案。c++11抛弃了auto_ptr,并新增了unique_ptr、shared_ptr、weak_ptr。

存在问题

#include <iostream>

void div(int b)
{
    int *a = new int(5);
    if (b == 0)
        throw std::invalid_argument("b can not equal to 0");

    printf("res = %d", *a / b);
}

int main()
{
    div(10);
    return 0;
}

当main函数结束的时候,所占的内存被释放。如果指向的内存也被释放多好。如果是一个有析构函数的类对象,则可以在对象过期的时候,让析构函数删除指向的内存。这正是auto_ptr,unique_ptr,shared_ptr背后的思想。

自己编写一个类来实现智能指针

#include <iostream>

template<typename T>
class SmartPtr{
private:
    T* _ptr;

public:
    SmartPtr(T *ptr): _ptr(ptr) {
        printf("Created!\n");
    }
    ~SmartPtr(){
        printf("deleted!\n");
        delete _ptr; 
    }

    T& operator*(){
        return *_ptr;
    }

    T* operator->(){
        return _ptr;
    }
};

auto_ptr存在的问题

  1. 指向同一段内存,存在两次析构的问题

    std::string *s1 = new std::string("abc");
    std::auto_ptr<std::string> ap1(s1);
    std::auto_ptr<std::string> ap2(s1);
    
    std::cout << *ap1 << std::endl;
    std::cout << *ap2 << std::endl;
    
  2. 所有权转移问题

std::string *s1 = new std::string("abc");
std::auto_ptr<std::string> ap1(s1);
std::auto_ptr<std::string> ap2;

ap2 = ap1;
std::cout << *ap2 << std::endl;

unique_ptr

unique_ptr是作用域指针,当超出作用域时,它会被销毁然后调用delete;

其不可复制,因为这会导致两个指针指向相同的内存,当其中一个将内存释放后,另一个便会指向已经被释放的内存,这会带来问题

要使用智能指针我们需要引入memory头文件

std::unique_ptr<Entity> entity1(new Entity());
std::unique_ptr<Entity> entity2 = std::make_unique<Entity>();

以上是两种unique_ptr的使用方式,后者对异常处理更加友好

需要注意的是unique_ptr的构造函数是explicit的,需要显式调用

使用unique_ptr声明的entity1entity2都会在作用域结束时被销毁

unique_ptr是最简单的智能指针,是有用且低开销的,缺点是其不能复制

std::unique_ptr<Entity> entity3 = entity2;<-为了防止我们写出这种代码,尝试去复制unique_ptrunique_ptr的复制构造函数和=操作符都被删除了,这样的代码会直接报错

shared_ptr

shared_ptr是共享指针,在大部分编译器中,其通过引用计数实现了智能指针的共享,每当共享指针被复制时,其内部的引用计数便会加一,当共享指针被释放时,引用计数便会减一,当引用数量为0时,共享指针才会真正释放其指向的内存

std::shared_ptr<Entity> entity1(new Entity());
std::shared_ptr<Entity> entity2 = std::make_shared<Entity>();

我们仍有两种方式使用shared_ptr,但对于shared_ptr来说,后者明显更好

unique_ptr中使用make_unique带来的优势仅仅是会抛出异常,但对于shared_ptr来说,因为其需要声明一块专用的控制块内存用于存储应用计数,通过new进行内存分配再传递给shared_ptr会带来两次内存分配,而使用make_shared则可以将两次内存分配组合在一起,获得更好的效率

weak_ptr

weak_ptr通常和shared_ptr一起使用,weak_ptr可以被复制,但是同时不会增加引用计数,仅仅声明这个指针还活着

同时weak_ptr可以查询其指向的内存块是否被释放了

std::weak_ptr<Entity> weak = entity2;

什么时候应该使用智能指针

根据实际情况而定,当你不需要手动管理内存时,智能指针会非常方便

在使用智能指针时,unique_ptr应该被优先考虑,因为其几乎没有开销,而当我们需要共享数据而unique_ptr无法实现时,shared_ptr就应该被使用了,即使其会带来一些额外的开销。

lambda表达式

lambda的概念

lambda表达式本质是一个匿名函数(因为它没有名字),恰当使用lambda表达式可以让代码变得简洁.并且可以提高代码的可读性

简单体会一下:

#include <iostream>
#include <vector>
#include <algorithm>

int main()
{

    std::vector<int> lst{1, 2, 3, 4, 5};
    std::sort(lst.begin(), lst.end(), [](int x, int y){
        return x>y;
    });

    for (const auto& item:lst){
        std::cout << item << " ";
    }
    return 0;
}

lambda的格式

[capture-list] (parameters) mutable -> return-type {statement };
 [ 捕捉列表 ] ( 形参 ) 约束(可选) -> 返回值类型(可选) {函数体}
  1. [capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量给lambda函数使用,如果没有变量需要捕捉,那[]里面的内容可以不写

  2. (parameters):参数列表, 与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略

  3. mutable:默认情况下,lambda函数总是一个const函数,(如果以传值方式方式捕获变量,是不可以修改的),但是可以使用mutable可以取消其常量性, 使用该修饰符时,参数列表不可省略(即使没有参数,也需要带上参数列表)

  4. return-type:返回值类型, 用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略,返回值类型明确情况下也可省略,由编译器对返回类型进行推导,所以一般不写返回值

  5. {statement}:函数体, 在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量

    lambda函数的参数列表和返回值类型都是可选部分,但捕捉列表和函数体是不可省略的,所以最简单的lambda为**[]{}**

  6. 捕获列表

    关于捕获列表

    捕获列表描述了上下文中哪些数据变量可以被lambda函数使用,以及使用的方式是传值还是传引用

    [var]:表示值传递方式捕捉变量var

    [=]:表示值传递方式捕获所有父作用域中的变量

    [&var]:表示引用传递捕捉变量var

    [&]:表示引用传递捕捉所有父作用域中的变量

lambda具体用法举例

  1. 最简单的lambda

    #include <iostream>
    
    int main()
    {
        []{printf("Hello, World!");}();
        return 0;
    }
    
  2. 值传递&引用传递

    #include <iostream>
    using namespace std;
    
    int main()
    {
        int a = 20;
        cout << "原始地址 = " << &a << endl;
    
        [=]{cout << "lambda val: " << &a << endl;}();
        [&]{cout << "lambda ref: " << &a << endl;}();
        return 0;
    }
    
  3. 值传递配合引用传递

    #include <iostream>
    using namespace std;
    
    int main(){
    
        int a = 20;
        int b = 10;
    
        cout << "outer &a = " << &a << endl;
        cout << "outer &b = " << &b << endl;
    
        [=, &a](){
            cout << "&a = " << &a << endl;
            cout << "&b = " << &b << endl;}();
        [=, &b](){
            cout << "&a = " << &a << endl;
            cout << "&b = " << &b << endl;}();
        return 0;
    }
    
  4. mutable使用

    #include <iostream>
    
    int main()
    {
    
        int a = 10;
        [=]() mutable {
            a = 20;
            std::cout << "a = " << a << std::endl;
        }();
        return 0;
    }
    
  5. 常用案例

    #include <iostream>
    #include <vector>
    #include <algorithm>
    
    int main()
    {
        std::vector<int> lst{5, 4, 3, 2, 1};
        std::sort(lst.begin(), lst.end(), [](int x, int y){return x < y;});
        std::for_each(lst.begin(), lst.end(), [](int v){std::cout << v << std::endl;});
        return 0;
    }
    

右值引用

右值引用是 C++11 引入的与 Lambda 表达式齐名的重要特性之一。它的引入解决了 C++ 中大量的历史遗留问题,消除了诸如 std::vector、std::string 之类的额外开销.提升了c++的效率。

左值和右值的概念

左值:一个表示数据的表达式(如变量名或解引用的指针),且可以获取他的地址(取地址),可以对它进行赋值;它可以在赋值符号的左边或者右边。

右值:一个表示数据的表达式(如字面常量、函数的返回值、表达式的返回值),且不可以获取他的地址(取地址);它只能在赋值符号的右边。

  1. 纯右值(prvalue, pure rvalue),纯粹的右值,要么是纯粹的字面量,例如 10, true;要么是求值结果相当于字面量或匿名临时对象,例如 1+2。非引用返回的临时变量、运算表达式产生的临时变量、原始字面量、Lambda 表达式都属于纯右值。
  2. 将亡值(xvalue, expiring value),是 C++11 为了引入右值引用而提出的概念(因此在传统 C++中,纯右值和右值是统一个概念),也就是即将被销毁、却能够被移动的值。

将亡值可能稍有些难以理解,我们来看这样的代码:

#include <iostream>

using namespace std;

int func(int x, int y) { return x + y; }

int main() {
  
  int a = 10;
  int b = a;
  const int d = 40; //右值具有const属性,临时值
  cout << &d << endl;
  
  //右值
  10;
  a+b;

  x + y;
  func(x, y);
  cout << &func(x, y) << endl; //不能取地址
  return 0;
}

左值引用和右值引用的区别

左值引用:给左值取别名 &

右值引用:给右值取别名 && ,右值引用的目的是为了延长用来初始化对象的生命周期

*std::move移动不了什么,唯一的功能是把左值强制转化为右值.

#include <algorithm>
#include <iostream>

using namespace std;

int func(int x, int y) { return x + y; }

int main() {
  //左值引用
  int a = 10;
  int &la = a;
  const int y = 10;
  int& rx2 = y;  // 非法:non-const引用不能被const左值初始化
    
  // const左值引用既可以引用左值,也可以引用右值
  const int b = 10;
  const int &lb = b;
  const int &lb1 = 10;
    
  //右值引用只能右值不能左值
  int &&r1 = 10;
  //右值引用可以引用move以后的左值
  int &&r2 = std::move(a);

  return 0;
}

总结

左值基本上是具有存储属性的对象,其具有地址和值,可以出现在=的左右两边

右值基本上是临时对象,如字面量与表达式,大部分情况下只能出现在=的右边,不能被赋值

左值引用,如std::string&,只能接受左值,除非加上const

右值引用,如std::sting&&,只能接受右值

// 返回值为int&类型,是左值引用,所以只能返回左值,即必须是具有存储空间,不能是临时变量
// 接受的参数为string&类型,是左值引用,所以接受的参数也必须是左值,直接Print("Hmxs")会报错
int& Print(std::string& name)
{
    static int value = 1;
    return value;
}

int main()
{
    std::string s = "Hmxs";
    Print(s);
}
std::string name1 = "wzh";
std::string name2 = "hmxs";
std::string name3 = name1 + name2;
// name1,name2,name3是左值
// "wzh","hmxs",name1+name2是右值
// 参数为右值引用,只能传入右值,传入左值会报错,即Print(name)会报错
void Print(std::string&& name) { }

int main()
{
    Print("wzh");
}

移动语义

移动语义是一种 C++ 中的编程技术,通过移动对象的资源(如动态分配的内存、文件句柄等)的所有权,以提高性能和避免不必要的资源复制。

std::move 是 C++ 标准库中的一个函数,用于将一个左值转换为右值引用,从而支持移动语义。移动语义允许在不进行深层复制的情况下转移资源的所有权,从一个对象转移到另一个对象。

具体来说,std::move 的作用是将给定的左值强制转换为右值引用,使得编译器可以选择调用移动构造函数而不是拷贝构造函数。这在一些情况下可以显著提高性能,尤其是涉及到资源管理的对象,比如动态分配的内存或文件句柄。

#include <iostream>
#include <cstring>

class String{
private:
    char* m_data;
    uint32_t m_size;

public:
    String() = default;

    // 构造函数
    String(const char* string){
        printf("Created!\n");
        m_size = strlen(string);
        m_data = new char[m_size];
        memcpy(m_data, string, m_size);
    }

    // 拷贝构造
    String(const String& string){
        printf("Copied!\n");
        m_size = string.m_size;
        m_data = new char[m_size];
        memcpy(m_data, string.m_data, m_size);
    }

    // 析构函数
    ~String(){
        delete m_data;
    }

    void print(){
        for (uint32_t i=0; i<m_size; ++i){
            printf("%c", m_data[i]);
        }
        printf("\n");
    }
};

class Entity{
private:
    String _name;

public:
    Entity(const String& name): _name(name) {}
    void printName(){
        _name.print();
    }
};

int main()
{
    Entity entity (String("cherno"));
    entity.printName();
    return 0;
}
移动构造函数

在上面这个例子中,如果我们不使用移动语义,那么复制对象几乎无法避免

但我们必须复制对象在这一情景下其实理应是荒谬的,我们的需求为将一个String对象放入Entity中,但我们需要做的却是先在外部创建一个String对象,再将其深拷贝到Entity的String中,为啥我们不能直接将外部创建的String直接移动到Entity中呢?而使用移动语义,我们便得以做到这点

移动语义实质上便是将深拷贝变为浅拷贝,我们需要编写移动构造函数,通过右值引用将对象强行标记为右值,即临时对象,后通过std::move使我们在拷贝时调用移动构造函数,而非复制构造函数

#include <iostream>
#include <cstring>

class String{
private:
    char* _data;
    uint32_t _size;

public:
    String() = default;
    String(const char* string){
        printf("Created!\n");
        _size = strlen(string);
        _data = new char[_size];
        memcpy(_data, string, _size);
    }

    String(const String& string){
        printf("Copied!\n");
        _size = string._size;
        _data = new char[_size];
        memcpy(_data, string._data, _size);
    }

    // 移动构造函数
    String(String&& string){
        printf("Moved!\n");
        _size = string._size;
        _data = string._data;

        // 处理原先的对象
        string._size = 0;
        string._data = nullptr;
    }

    ~String(){
        printf("Destoryed\n");
        delete _data;
    }

    void print(){
        for (uint32_t i=0; i<_size; ++i){
            printf("%c", _data[i]);
        }
        printf("\n");
    }
};

class Entity{
private:
    String _name;

public:
    Entity(const String& name): _name(name) {}

    Entity(String&& name): _name(std::move(name)) {}

    void printName(){
        _name.print();
    }
};

int main(){

    Entity entity(String("cherno"));
    entity.printName();
    return 0;
}

通过上面的代码,我们便成功避免了多余的复制,其中Entity entity(String("Hmxs"));具体的逻辑是:

  1. 首先String("Hmxs")会创建一个临时的String对象,这一对象传入entity的构造函数中
  2. 因为传入的String对象是右值,所以会优先进入Entity(String&& name)这一参数为右值引用,并执行name_(std::move(name))的初始化过程

作为右值引用被传入的String对象在进入函数后变为了左值,所以需要使用std::move将其强转为右值引用,才能进入String的移动构造函数

  1. 之后便进入了String的移动构造函数中,将entity中的name_指向临时的String对象,之后将临时的String对象置空,即完成了移动过程

因为临时的String对象是右值,且被置空,在语句结束后会自动地进行释放

至此,我们便完成了一次成功的移动语义,cool

移动赋值运算符

在上一章中,我们通过编写移动构造函数实现了移动语义,而正如其名字中的构造所言,其是一种类型的构造函数,只有在对象构造时才会被调用

那么如果我们想要对一个已经存在的对象使用移动语义呢?我们应该使用移动赋值操作符

#include <iostream>
#include <cstring>

class String{
private:
    char* _data;
    uint32_t _size;

public:
    String() = default;
    String(const char* data) {
        printf("Created!\n");
        _size = strlen(data);
        _data = new char[_size];
        memcpy(_data, data, _size);
    }

    String(const String& string) {
        printf("Copied!\n");
        _size = string._size;
        _data = new char[_size];
        memcpy(_data, string._data, _size);
    }

    String& operator=(const String& string) {
        printf("assign!\n");
        _size = string._size;
        _data = new char[_size];
        memcpy(_data, string._data, _size);
        return *this;
    }

    ~String(){
        printf("Destroyed!\n");
        delete _data;
    }

    // 移动构造函数
    String(String&& string) {
        printf("move copy!\n");
        _size = string._size;
        _data = string._data;

        string._size = 0;
        string._data = nullptr;
    }

    // 移动赋值函数
    String& operator=(String&& string) {
        printf("move assign!\n");
        // 如果尝试给自己赋值,则直接返回
        if (this == &string) {
            return *this;
        }

        // 移动赋值操作会作用于一个已经存在的对象,我们应该先释放原先的对象
        delete[] _data;

        _data = string._data;
        _size = string._size;

        // 处理原先的对象
        string._data = nullptr;
        string._size = 0;

        return *this;
    }

};

int main() {

    // String s("abc");
    // String s1(std::move(s)); // move copy

    String s("abc");
    String dest("bcd");

    dest = std::move(s); // move assign
    return 0;
}

完美转发 std::forward

  1. 万能引用

    模板中的&& , 不代表右值引用,而是万能引用,其既能接收左值又能接收右值。

    #include <iostream>
    
    template<typename T>
    void func(T && val){
        std::cout << val << std::endl;
    }
    
    int main(){
        
        int a1 = 3;
        // int &&a = a1;
        int &&b = 3;
    
        func(4);
        func(b);
        func(a1);
        func(std::move(a1));
        return 0;
    }
    
  2. 完美转发

    #include <iostream>
    
    template<typename T>
    void func(T &val){
        std::cout << "左值引用: " << val << std::endl;
    }
    
    template<typename T>
    void func(T && val){
        std::cout << "右值引用: " << val << std::endl;
    }
    
    template <typename T>
    void func1(T && val) {
        func(std::forward<T>(val));
    }
    
    int main() {
    
        int a = 10;
        func1(a);
        func1(10);
        func1(std::move(a));
        return 0;
    }
    

可调用对象

一组执行任务的语句都可以视为一个"函数",一个可调用对象。

在C++中就func的类型可以为:

  • 普通函数
  • 类成员函数
  • 类静态函数
  • 仿函数
  • 函数指针
  • lambda表达式 C++11加入标准

普通函数

#include <iostream>

typedef int(* FUNC)(int, int);
using FUNC1 = int(*)(int, int);

int func(int x, int y) {
    return x + y;
} 

int main() {
    FUNC f1 = func;
    FUNC1 f2 = func;

    std::cout << f1(5, 5) << std::endl;
    std::cout << f2(5, 10) << std::endl;
    return 0;
}

类成员函数

#include <iostream>

class MyClass{
public:
    int add(int x, int y) {
        return x + y;
    }
};

typedef int(MyClass::*FUNC)(int, int);
using FUNC1 = int(MyClass::*)(int, int);

int main() {

    FUNC1 f = MyClass::add;
    MyClass m;
    std::cout << (m.*f)(5, 5) << std::endl;
    return 0;
}

类静态函数

#include <iostream>

class MyClass {
public:
    static int add(int x, int y);
};

int MyClass::add(int x, int y) {
    return x + y;
}

using FUNC = int(*)(int, int);

int main() {
    
    FUNC f = MyClass::add;
    std::cout << f(5, 5) << std::endl;
    return 0;
}

仿函数

函数指针

lambda表达式

function和bind

function的概念

std::function是一个函数包装器模板,在c++11中,将function纳入标准库中,该函数包装器模板能包装任何类型的可调用元素.

一个std::function类型对象实例可以包装下列这几种可调用元素类型:函数、函数指针、类成员函数指针或任意类型的函数对象(例如定义了operator()操作并拥有函数闭包)

基本格式:

**function<return-type(type1,type2)> func**

作用:实现接口统一

function的使用

#include <iostream>
#include <functional>
#include <map>
#include <string>

int add(int x, int y) { 
	return x + y; 
}

int sub(int x, int y) {
	return x - y;
}

void func(std::function<int(int, int)> f, int x, int y) {
	std::cout << f(x, y) << std::endl;
}

int main() {

	std::function<int(int, int)> f1 = add;
	std::function<int(int, int)> f2 = sub;
	std::function<int(int, int)> f3 = [](int x, int y) { return x * y; };

	func(f1, 5, 5);
	func(f2, 5, 10);
	func(f3, 5, 15);

	std::map<std::string, std::function<int(int, int)>> m{ {"+", f1}, {"-", f2}, {"*", f3}};
	std::cout << m["+"](10, 20) << std::endl;

	return 0;
}

bind

bind是一个标准库函数,定义在functional头文件中。可以将bind函数看作一个通用的函数适配器,它接受一个可调用对象,生成新的可调用对象来适应原对象的参数列表。

template< class F, class... Args >
bind( F&& f, Args&&... args );
绑定普通函数
#include <iostream>
#include <functional>

class MyClass {
public:
	MyClass() {
		std::cout << "Created!" << std::endl;
	}

	MyClass(const MyClass& other) {
		std::cout << "Copy!" << std::endl;
	}

	~MyClass() {
		std::cout << "Destroy!" << std::endl;
	}

	int add(int x, int y) {
		return x + y;
	}
};

int main() {
	
	using FUNC = int(MyClass::*)(int, int);
	MyClass m;
	FUNC f = &MyClass::add;

	std::function<int(int)> func = std::bind(f, &m, std::placeholders::_1, 5);
	std::cout << func(3) << std::endl;
	return 0;
}
绑定类成员函数
#include <iostream>
#include <functional>

class MyClass {
public:
	MyClass() {
		std::cout << "Created!" << std::endl;
	}

	MyClass(const MyClass& other) {
		std::cout << "Copy!" << std::endl;
	}

	~MyClass() {
		std::cout << "Destroy!" << std::endl;
	}

	int add(int x, int y) {
		return x + y;
	}
};

int main() {
	
	using FUNC = int(MyClass::*)(int, int);
	MyClass m;
	FUNC f = &MyClass::add;

	std::function<int(int)> func = std::bind(f, &m, std::placeholders::_1, 5);
	std::cout << func(3) << std::endl;
	return 0;
}
绑定类静态成员函数
#include <iostream>
#include <functional>

class MyClass {
public:
	MyClass() {
		std::cout << "Created!" << std::endl;
	}

	MyClass(const MyClass& other) {
		std::cout << "Copy!" << std::endl;
	}

	~MyClass() {
		std::cout << "Destroy!" << std::endl;
	}

	static int add(int x, int y);
};

int MyClass::add(int x, int y) {
	return x + y;
}

int main() {

	using FUNC = int(MyClass::*)(int, int);
	// 无需类的对象
	auto f = std::bind(MyClass::add, 10, 10);
	std::cout << f() << std::endl;
	return 0;
}

std::ref

注意:

  • bind预先绑定的参数需要传具体的变量或值进去,对于预先绑定的参数,是pass-by-value的;
  • 对于不事先绑定的参数,需要传std::placeholders进去,从_1开始,依次递增。placeholder是pass-by-reference的;

如果要传递引用,则要使用std::ref()

#include <iostream>
#include <functional>

void func(int& x) {
	++x;
	std::cout << x << std::endl;
}

int main() {

	int a = 10;
	auto f = std::bind(func, std::ref(a));
	f();

	std::cout << a << std::endl;
	return 0;
}

std::ref

std::ref主要在函数式编程(如std::bind)时使用,bind是对参数直接拷贝,无法传入引用(即使你传入的实参是引用类型也不行),故引入std::ref()。使用std::ref可以在模板传参的时候传入引用。

ref能使用reference_wrapper包装好的引用对象代替原本会被识别的值类型,而reference_wrapper能隐式转换为被引用的值的引用类型。

bind,如果要传递引用,则要使用std::ref()

#include <iostream>
#include <functional>

void func(int& x) {
	++x;
	std::cout << x << std::endl;
}

int main() {

	int a = 10;
	auto f = std::bind(func, std::ref(a));
	f();

	std::cout << a << std::endl;
	return 0;
}
  • 18
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值