【C++】-- C++11基础常用知识点(下)

news2024/10/5 16:25:12

上篇: 【C++】-- C++11基础常用知识点(上)_川入的博客-CSDN博客


目录

新的类功能

默认成员函数

可变参数模板

可变参数

可变参数模板

empalce

lambda表达式

C++98中的一个例子

lambda表达式

lambda表达式语法

捕获列表

lambda表达底层

包装器

function包装器

bind绑定


新的类功能

默认成员函数

原来C++类中,有6个默认成员函数:
  1. 构造函数
  2. 析构函数
  3. 拷贝构造函数
  4. 拷贝赋值重载
  5. 取地址重载
  6. const 取地址重载

        最后重要的是前4个,后两个用处不大。默认成员函数就是我们不写编译器会生成一个默认的。 C++11 新增了两个:移动构造函数和移动赋值运算符重载

        所以到了C++11后有8个默认成员函数。

移动构造函数和移动赋值运算符重载的又来以及原理:
【C++】-- C++11 - 右值引用和移动语义(上万字详细配图配代码从执行一步步讲解)_川入的博客-CSDN博客

只有在深拷贝的情况下才会有移动构造函数移动赋值运算符重载。可以认为:

  • 拷贝构造函数与拷贝赋值重载:针对于左值的拷贝。
  • 移动构造函数和移动赋值重载:针对于右值的拷贝。

        移动构造函数移动赋值重载,编译器自行生成的默认成员函数,能用的条件的复杂度与苛刻程度远远大于:构造函数、析构函数 、拷贝构造函数 、拷贝赋值重载4个默认成员函数。(由于:取地址重载 、const 取地址重载几乎不用自己写,用编译器的即可,所以忽略)

针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下:
  • 编译器生成默认移动构造函数条件

        没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。

  • 编译器生成默认移动构造函数实现

        默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝。自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。

  • 编译器生成默认动赋值重载函数条件
        你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。
  • 编译器生成默认动赋值重载函数实现
         默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋
值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)。
  • 如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。
强制生成默认函数的关键字default:
 
        C++11可以让我们更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用default关键字显示指定移动构造生成。
class Person
{
public:
    Person(const char *name = "", int age = 0)
        : _name(name), _age(age)
    {}

    Person(const Person &p)
        : _name(p._name), _age(p._age)
    {}

    Person(Person &&p) = default;

private:
    bit::string _name;
    int _age;
};

int main()
{
    Person s1;
    Person s2 = s1;
    Person s3 = std::move(s1);
    return 0;
}
禁止生成默认函数的关键字delete:
        如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明补丁已,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。
class Person
{
public:
    Person(const char *name = "", int age = 0)
        : _name(name), _age(age)
    {}

    Person(const Person &p) = delete;

private:
    bit::string _name;
    int _age;
};
int main()
{
    Person s1;
    Person s2 = s1;
    Person s3 = std::move(s1);
    return 0;
}

        可以使用default关键字强行让编译器生成,但是需要注意析构函数 、拷贝构造、拷贝赋值重载也会收到影响,需要自己写或也强制生成。没有什么意义,所以一般default关键字是用于构造,因为拷贝构造也属于构造,如果写了拷贝构造就不会默认生成构造了。

#问:如何用delete关键字实现一个类,只能再堆上创建对象?

        平时我们创建的类,是可以在栈区、全局数据区上创建的。

class HeapOnly
{

};

int main()
{
    HeapOnly hp1;  // 栈区
    static HeapOnly h2; // 全局数据区
    return 0;
}

        我们可以通过delete析构函数,然后使用new开辟类。


class HeapOnly
{
public:
    // HeapOnly()
    // {
    //     str_ = new char[10];
    // }

    // void Destroy()
    // {
    //     delete[] str_;
    //     operator delete(this); // 内存管理之重载operator delete
    // }

    ~HeapOnly() = delete;
private:
    char* str_;
};

int main()
{
    // HeapOnly hp1;  // 栈区 -- 会调析构
    // static HeapOnly h2; // 全局数据区 -- 会调析构
    

    // new出来的对象会调用构造 -- 这个时候会导致资源泄漏
    HeapOnly *ptr = new HeapOnly;
    operator delete(ptr);

    return 0;
}
  • new是c++中的操作符,malloc是c中的一个函数。
  • new不止是分配内存,而且会调用类的构造函数,同理delete会调用类的析构函数
  • malloc只会单纯的分配内存,不会进行初始化类成员的工作,同样free也不会调用析构函数。

#问:

class HeapOnly
{
public:
    HeapOnly()
    {
        str_ = new char[10];
    }

    ~HeapOnly() = delete;
private:
    char* str_;
};

        对于构造函数是new空间,因为不能调用析构而不能使用delete,导致值空间泄漏怎么办?

        我们可以搞一个函数,利用函数将其释放。

class HeapOnly
{
public:
    HeapOnly()
    {
        str_ = new char[10];
    }

    void Destroy()
    {
        delete[] str_;
        operator delete(this); // 内存管理之重载operator delete
        // 也可以使用free
    }

    ~HeapOnly() = delete;
private:
    char* str_;
};

int main()
{
    // HeapOnly hp1;  // 栈区 -- 会调析构
    // static HeapOnly h2; // 全局数据区 -- 会调析构
    
    // new出来的对象会调用构造 -- 这个时候会导致资源泄漏
    HeapOnly *ptr = new HeapOnly;

    ptr->Destroy();

    return 0;
}

        继承的时候要小心,因为指针是可能出现偏移的,继承之后,切片可能成员位置发生变化,operator delete(this);的释放位置就可能不对。

可变参数模板

可变参数

可变参数最早的出现是在C语言:

         以printf,不确定参数传多少个参数,后面可以传一串值,也就可变参数,可以有0 ~ n个参数。底层是用数组实现的。

可变参数模板

下面就是一个基本可变参数的函数模板:
// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>
void ShowList(Args... args)
{}

// (不一定非要写作:Args、args,可以换一个名字,只是这两个常用)
#include <string>

// 可变参数的函数模板
template <class ...Args>
void ShowList(Args... args)
{}

int main()
{
	std::string str("hello");
	ShowList();
	ShowList(1);
	ShowList(1, 'A');
	ShowList(1, 'A', str);

	return 0;
}
        上面的参数args前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为“参数包”,它里面包含了0到N(N>=0)个模版参数。我们无法直接获取参数包args中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。
        如果,我们想拿到参数包里面的参数,是不好拿的, sizeof 可以帮助我们算参数包里面有多少个参数:
#include <string>
#include <iostream>

// 可变参数的函数模板
template <class ...Args>
void ShowList(Args... args)
{
    std::cout << sizeof...(args) << std::endl;
}

int main()
{
	std::string str("hello");
	ShowList();
	ShowList(1);
	ShowList(1, 'A');
	ShowList(1, 'A', str);

	return 0;
}
Note:
for(int i = 0; i< sizeof...(args); i++)
{
    std::cout << args[i] << " "; // error:args[i]不支持
}

        语法不支持使用args[i]这样方式获取可变参数,所以我们需要用一些奇招来 一一 获取参数包的值。

第一种:递归函数方式展开参数包

        将参数包改一改,增加一个参数。

#include <iostream>
#include <string>

// 递归终止函数
template <class T>
void ShowList()
{
    std::cout << std::endl;
}

// 展开函数
template <class T, class... Args>
void ShowList(const T& value, Args... args) // 第一个参数传给value,剩下的传给参数包args。
{
    cout << value << " ";
    ShowList(args...); // 参数超过0个递归调自己,参数0个调递归终止函数。
}

int main()
{
    ShowList(1);
    ShowList(1, 'A');
    ShowList(1, 'A', std::string("sort"));
    return 0;
}

        利用递归不断地推出参数包中的内容。

第二种:逗号表达式展开参数包

        这种展开参数包的方式,不需要通过递归终止函数,是直接在ShowList函数体中展开的, PrintArg不是一个递归终止函数,只是一个处理参数包中每一个参数的函数。这种就地展开参数包的方式实现的关键是逗号表达式,因为逗号表达式会按顺序执行逗号前面的表达式。

#include <iostream>
#include <string> 

template <class T>
void PrintArg(cosnt T t)
{
    std::cout << t << " ";
}

// 展开函数
template <class... Args>
void ShowList(Args... args)
{
    // 利用逗号表达式去初始化arr,arr编译的时候就会知道要开多大,这个时候就会依次展开args参数包。
    // 利用逗号表达式去取右边的值0。(逗号表达式会按顺序执行逗号前面的表达式)
    int arr[] = {(PrintArg(args), 0)...};
    std::cout << std::endl;
}

int main()
{
    ShowList(1);
    ShowList(1, 'A');
    ShowList(1, 'A', std::string("sort"));
    return 0;
}

同理,也可以优化为不适用逗号表达式展开参数包:

#include <iostream>
#include <string> 

template <class T>
int PrintArg(cosnt T t)
{
    std::cout << t << " ";
    return 0; 
}

// 展开函数
template <class... Args>
void ShowList(Args... args)
{
    // arr编译的时候就会知道要开多大,这个时候就会依次展开args参数包。
    int arr[] = { PrintArg(args)... };
    std::cout << std::endl;
}

int main()
{
    ShowList(1);
    ShowList(1, 'A');
    ShowList(1, 'A', std::string("sort"));
    return 0;
}

empalce

        分析STL容器中的empalce相关接口函数:

https://cplusplus.com/reference/vector/vector/emplace/

https://cplusplus.com/reference/vector/vector/emplace_back/
https://cplusplus.com/reference/list/list/emplace_back/
        以vector容器的emplace_back为例:

          emplace_back是在一个函数模板里面,把一个成员函数是实现成可变参数包。其就是通过将可变参数包不断不断的往下传,传到最下面去初始化对应数据,或者是链表的话就初始化节点里的数据。

template <class... Args>
void emplace_back (Args&&... args);
        首先我们看到的emplace系列的接口,支持模板的可变参数,并且万能引用/引用折叠 (即可以引用左值,也可以引用右值)
#问:那么相对insert 和emplace系列接口的优势到底在哪里呢?
// vector::emplace_back
#include <iostream>
#include <vector>

int main ()
{
    std::vector<int> myvector;

    myvector.push_back(100);
    myvector.emplace_back(200);

  return 0;
}
        如果只是简单的int的,其与push_back就没有什么区别。主要的区别在于:
// vector::emplace_back
#include <iostream>
#include <vector>
#include <string>
#include <utility>

int main()
{
    std::vector<std::pair<std::string, int>> myvector;

    myvector.push_back(std::make_pair("sort", 1));

    myvector.emplace_back(std::make_pair("sort", 1));
    myvector.emplace_back("sort", 1);

    return 0;
}

       效率上就emplace_back更好,因为make_pair是先构造,构造了一个pair。如此push_back就传了一个pair对象。所以调push_back是:

  • 左值:构造 + 拷贝构造。
  • 右值:构造 + 移动构造。

        emplace_back是不用着急创建pair对象,我们可将这个参数包一直向下传递,直到最后需要插入数据的时候,直接用这个数据包创建pair对象。

  • 直接构造。

        所以emplace系列比insert系列接口不一定高效。

通过代码凸显区别:

        不一定所有容器都会出现,于源码的实现有关系,此处使用list容器,并在VS2019实现出来的:

#include <iostream>
#include <list>
#include <string>

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)
	{
		std::cout << "Date(int year = 1, int month = 1, int day = 1)" << std::endl;
	}

	Date(const Date& d)
		:_year(d._year)
		, _month(d._month)
		, _day(d._day)
	{
		std::cout << "Date(const Date& d)" << std::endl;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	std::list<Date> lt1;
	lt1.push_back(Date(2022, 11, 16));

	std::cout << "---------------------------------" << std::endl;
    
	lt1.emplace_back(2022, 11, 16);

	return 0;
}

        所以建议:这个这种场景下直接使用emplace系列接口。

lambda表达式

        lambda也叫做匿名函数。

像函数使用的对象 / 类型:

  1. 函数指针 -- C++不喜欢的操作,所以有了仿函数。(全局的函数)
  2. 仿函数 / 函数对象。(全局的类)
  3. lambda。(局部)

 C++98中的一个例子

        因为由于仿函数有诸多的不便。如果待排序元素为自定义类型,需要用户定义排序时的比较规则,对于以下的三个成员一个就要创建2个(less、greater),就是6个。

#include <string>

struct Goods
{
	std::string _name;  // 名字
	double _price; // 价格
	int _evaluate; // 评价

	Goods(const char* str, double price, int evaluate)
		:_name(str)
		, _price(price)
		, _evaluate(evaluate)
	{}
};
        随着C++语法的发展,人们开始觉得上面的写法太复杂了,每次为了实现一个algorithm算法,都要重新去写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,这些都给编程者带来了极大的不便。因此,在C++11语法中出现了Lambda表达式。

lambda表达式

lambda表达式语法

lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type { statement }

1. lambda表达式各部分说明:
  • [capture-list] : 捕捉列表。该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数捕捉列表能够捕捉上下文中的变量供lambda函数使用
  • (parameters):参数列表。普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略(无参时可以省略)
  • mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。
  • ->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推
  • {statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。

注意:

        在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为 。因此C++11中最简单的lambda函数为: []{}; 该lambda函数不能做任何事情。
#include <iostream>

int main()
{
    // 两个数相加的lambda
    // 没有函数名,加一个捕捉列表[]而已。因为没有名字,所以调用不好调
    // 但是[](int a, int b) -> int{ return a + b; }整体是一个对象,所以就可以巧用auto。
    auto add1 = [](int a, int b) -> int{ return a + b; };
    std::cout << add1(1, 2) << std::endl;

    // 省略返回值
    auto add2 = [](int a, int b){ return a + b; };
    std::cout << add2(1, 2) << std::endl;
}

        于是对于前面的三个成员一个就要创建2个(less、greater),就是6个。解决:

#include <string>
#include <vector>
#include <algorithm>

struct Goods
{
    std::string _name; // 名字
    double _price;     // 价格
    int _evaluate;     // 评价
    //...

    Goods(const char *str, double price, int evaluate)
        : _name(str), _price(price), _evaluate(evaluate)
    {
    }
};

int main()
{
    std::vector<Goods> v = {{"苹果", 2.1, 5}, {"香蕉", 3, 4}, {"橙子", 2.2, 3}, {"菠萝", 1.5, 4}};

    sort(v.begin(), v.end(), [](const Goods &g1, const Goods &g2)
         { return g1._name < g2._name; });

    sort(v.begin(), v.end(), [](const Goods &g1, const Goods &g2)
         { return g1._name > g2._name; });

    sort(v.begin(), v.end(), [](const Goods &g1, const Goods &g2)
         { return g1._price < g2._price; });

    sort(v.begin(), v.end(), [](const Goods &g1, const Goods &g2)
         { return g1._price > g2._price; });

    sort(v.begin(), v.end(), [](const Goods &g1, const Goods &g2)
         { return g1._evaluate < g2._evaluate; });

    sort(v.begin(), v.end(), [](const Goods &g1, const Goods &g2)
         { return g1._evaluate > g2._evaluate; });
}

#问:如何写一个交换swap函数?

        可以像上面那样写,但是会非常的难看。

#include <iostream>

int main()
{
    // 交换变量的lambda - 行数会多
    int x = 0, y = 1;
    auto swap1 = [](int &x1, int &x2) -> void{int tmp = x1; x1 = x2; x2 = tmp; };
    swap1(x, y);
    std::cout << x << ":" << y << std::endl;
}

        我们可以这样写:

#include <iostream>

int main()
{
    // 交换变量的lambda - 行数会多
    int x = 0, y = 1;
    auto swap1 = [](int &x1, int &x2) -> void
    {
        int tmp = x1; 
        x1 = x2; 
        x2 = tmp; 
    };
    
    swap1(x, y);
    std::cout << x << ":" << y << std::endl;
}

捕获列表

#问:假如我们想不传参数交换x,y呢?

利用捕捉列表实现,注意:

  • 想捕捉谁就写谁,只能捕捉跟lambda表达式同一个作用域的对象。
  • 默认捕捉过来的变量不能修改 —— 加mutable让捕捉过来的变量可以修改(使用mutable须加())。
  • 默认捕捉是拷贝的方式捕捉,严格意义上说是传值捕捉。(lambda还是一个函数调用,是有栈帧的 —— 可以理解为:改变形参,不会改变实参)
#include <iostream>

int main()
{
    // 交换变量的lambda - 行数会多
    int x = 0, y = 1;

    // 可以理解为:改变形参,不会改变实参
    auto swap = [x, y]()mutable
    {
        int tmp = x; 
        x = y; 
        y = tmp; 
    };
    
    swap();
    std::cout << x << ":" << y << std::endl;
}

        所以mutable在实际中不起价值作用。

捕获列表说明:

捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用。

  • [var]:表示值传递方式捕捉变量var。
  • [=]:表示值传递方式捕获所有父作用域中的变量(包括this)。
  • [&var]:表示引用传递捕捉变量var。
  • [&]:表示引用传递捕捉所有父作用域中的变量(包括this)。
  • [this]:表示值传递方式捕捉当前的this指针。

注意:

  • 父作用域指包含lambda函数的语句块
  • 语法上捕捉列表可由多个捕捉项组成,并以逗号分割
    • 比如:
      • [=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量。
      • [&, a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量。
  • 捕捉列表不允许变量重复传递,否则就会导致编译错误
    • 比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复
  • 在块作用域以外的lambda函数捕捉列表必须为空
  • 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错。
  • lambda表达式之间不能相互赋值,即使看起来类型相同
#include <iostream>

void (*PF)();

int main()
{
    auto f1 = []
    { std::cout << "hello world" << std::endl; };
    auto f2 = []
    { std::cout << "hello world" << std::endl; };

    // f1 = f2;   // 编译失败--->提示找不到operator=()

    // 允许使用一个lambda表达式拷贝构造一个新的副本
    auto f3(f2);
    f3();

    // 可以将lambda表达式赋值给相同类型的函数指针
    PF = f2;
    PF();

    return 0;
}

lambda表达底层

        函数对象,又称为仿函数,即可以想函数一样使用的对象,就是在类中重载了operator()运算符的类对象,与范围for很像。

范围for:

        并没有看起来这么的智能,实际上是底层运用迭代器实现的。

class Rate
{
public:
    Rate(double rate) : _rate(rate)
    {}

    double operator()(double money, int year)
    {
        return money * _rate * year;
    }

private:
    double _rate;
};

int main()
{
    // 函数对象
    double rate = 0.49;
    Rate r1(rate);
    r1(10000, 2);

    // lamber
    auto r2 = [=](double monty, int year) -> double
    {
        return monty * rate * year;
    };

    r2(10000, 2);
    return 0;
}

        仿函数的名称就是:lambda_uuid。所以lambda表达式对于我们是匿名的,对于编译器而言是有名的。实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()。

包装器

function包装器

         function包装器也叫作适配器,C++中的function本质是一个类模板,也是一个包装器。
#include <iostream>

template <class F, class T>
T useF(F f, T x)
{
    static int count = 0;
    cout << "count:" << ++count << endl;
    cout << "count:" << &count << endl;
    return f(x);
}
double f(double i)
{
    return i / 2;
}
struct Functor
{
    double operator()(double d)
    {
        return d / 3;
    }
};
int main()
{
    // 函数名
    std::cout << useF(f, 11.11) << std::endl;

    // 仿函数对象
    std::cout << useF(Functor(), 11.11) << std::endl;

    // lamber表达式对象
    std::cout << useF([](double d)->double{ return d/4; }, 11.11) << std::endl;
    return 0;
}

        因为上述的 f 的类型不同,于是会被实例化成三个

        包装器可以很好的解决上面的问题,将其变为1份。

std::function在头文件<functional>
// 类模板原型如下
template <class T> function;     // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;
模板参数说明:
Ret: 被调用函数的返回类型
Args…:被调用函数的形参
使用方法:
// 使用方法如下:
#include <functional>
#include <iostream>

int f(int a, int b)
{
    return a + b;
}

struct Functor
{
public:
    int operator()(int a, int b)
    {
        return a + b;
    }
};

int main()
{
    // 函数名(函数指针)
    std::function<int(int, int)> func1 = f;
    std::cout << func1(1, 2) << std::endl;

    // 函数对象
    std::function<int(int, int)> func2 = Functor();
    std::cout << func2(1, 2) << std::endl;

    // lamber表达式
    std::function<int(int, int)> func3 = [](const int a, const int b)
    { return a + b; };
    std::cout << func3(1, 2) << std::endl;

    return 0;
}

        对于静态成员函数与非静态成员函数的不同:

//使用方法如下:
#include <functional>
#include <iostream>

class Plus
{
public:
    static int plusi(int a, int b)
    {
        return a + b;
    }
    double plusd(double a, double b)
    {
        return a + b;
    }
};

int main()
{
    //类的成员函数 -- 语法规定
    // 静态成员函数可以不用加&,可以加&。并且可以直接调用。
    std::function<int(int, int)> func4 = Plus::plusi; 
    std::cout << func4(1, 2) << std::endl;

    // 非静态成员函数需要加&,并且不能直接调用,需要传对象,此处为Plus。(成员函数多传一个)
    std::function<double(Plus, double, double)> func5 = &Plus::plusd; 
    std::cout << func5(Plus(), 1.1, 2.2) << std::endl;

    return 0;
}

        如果对于非静态成员函数,不想多传一个类对象的参数,可以通过绑定的方式解决这个问题。

        所以对于上面的,因为上述的 f 的类型不同,于是会被实例化成三个,就可以解决了:

#include <iostream>
#include <functional>

template <class F, class T>
T useF(F f, T x)
{
    static int count = 0;
    std::cout << "count:" << ++count << std::endl;
    std::cout << "count:" << &count << std::endl;
    return f(x);
}
double f(double i)
{
    return i / 2;
}
struct Functor
{
    double operator()(double d)
    {
        return d / 3;
    }
};

int main()
{
	// 函数指针
	std::function<double(double)> f1 = f;
	std::cout << useF(f1, 11.11) << std::endl;

	// 函数对象
	std::function<double(double)> f2 = Functor();
	std::cout << useF(f2, 11.11) << std::endl;

	// lamber表达式对象
	std::function<double(double)> f3 = [](double d)->double{ return d / 4; };
	std::cout << useF(f3, 11.11) << std::endl;

	return 0;
}

包装器的其他一些场景:
 

class Solution
{
public:
    int evalRPN(vector<string> &tokens)
    {
        stack<long long> st;
        map<string, function<long long(long long, long long)>> opFuncMap =
            {
                {"+", [](long long i, long long j)
                 { return i + j; }},
                {"-", [](long long i, long long j)
                 { return i - j; }},
                {"*", [](long long i, long long j)
                 { return i * j; }},
                {"/", [](long long i, long long j)
                 { return i / j; }}};
        for (auto &str : tokens)
        {
            if (opFuncMap.find(str) != opFuncMap.end())
            {
                long long right = st.top();
                st.pop();
                long long left = st.top();
                st.pop();
                st.push(opFuncMap[str](left, right));
            }
            else
            {
                // 1、atoi itoa
                // 2、sprintf scanf
                // 3、stoi to_string C++11
                st.push(stoll(str));
            }
        }
        return st.top();
    }
};

bind绑定

        std::bind函数定义在头文件中,是一个函数模板,它就像一个函数包装器(适配器)接受一个可调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。一般而言,我们用它可以把一个原本接收N个参数的函数fn,通过绑定一些参数,返回一个接收M个(M可以大于N,但这么做没什么意义)参数的新函数。同时,使用std::bind函数还可以实现参数顺序调整等操作。

// 原型如下:
template <class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);

// with return type (2) 
template <class Ret, class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);

调用bind的一般形式:auto newCallable = bind(callable,arg_list);

库中就是使用了placeholders来占位:

https://legacy.cplusplus.com/reference/functional/placeholders/

        其中的_1、_2、_3等,就是用来占位的。_1代表第1个参数,_2代表第2个参数……。调整的是形参的顺序。

#include <functional>
#include <iostream>

int Div(int a, int b)
{
	return a / b;
}

int main()
{
	int x = 10, y = 2;
	std::cout << Div(x, y) << std::endl;

	// 调整顺序 -- 鸡肋,一般用不上
	// _1, _2.... 定义在placeholders命名空间中,代表绑定函数对象的形参,
	// _1,_2... 分别代表第一个形参、第二个形参...

	//std::function<int(int, int)> bindFunc = bind(Div, std::placeholders::_2, std::placeholders::_1);
	auto bindFunc = bind(Div, std::placeholders::_2, std::placeholders::_1);

    // 传时候不会变
	std::cout << bindFunc(x, y) << std::endl;
	return 0;
}

可以理解为:

// x -> _1 ->a
// y -> _2 ->b。
auto bindFunc = bind(Div, _1, _2);
bindFunc(x, y);

// x -> _2 ->b
// y -> _1 ->a。
auto bindFunc = bind(Div, _2, _1);
bindFunc(x, y);

        可以用绑定解决前面的非静态成员函数,需要传类对象(成员函数多传一个),以绑定参数解决 -> 调整个数。

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

int Plus(int a, int b)
{
	return a + b;
}

int Mul(int a, int b, double rate)
{
	return a * b * rate;
}

class Sub
{
public:
	int sub(int a, int b)
	{
		return a - b;
	}
};

// 11:50继续
int main()
{
	// 调整个数, 绑定死固定参数
	std::function<int(int, int)> funcPlus = Plus;

    // 本来要传3个.
	// function<int(Sub, int, int)> funcSub = &Sub::sub;
    // 将其变为只传2个,将1个(此处Sub())固定在这个地方绑死 — 不能变。
	std::function<int(int, int)> funcSub = std::bind(&Sub::sub, Sub(), std::placeholders::_1, std::placeholders::_2);

    // 1.5就固定死了
	std::function<int(int, int)> funcMul = std::bind(Mul, std::placeholders::_1, std::placeholders::_2, 1.5);

	std::map<std::string, std::function<int(int, int)>> opFuncMap = 
	{
		{ "+", Plus},
		{ "-", std::bind(&Sub::sub, Sub(), std::placeholders::_1, std::placeholders::_2)}
	};

	std::cout << funcPlus(1, 2) << std::endl;
	std::cout << funcMul(2, 2) << std::endl;
	std::cout << funcSub(1, 2) << std::endl;

	std::cout << opFuncMap["+"](1, 2) << std::endl;
	std::cout << opFuncMap["-"](1, 2) << std::endl;

	return 0;
}

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

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

相关文章

【Pytorch项目实战】基于PaddlenHub的口罩检测与语音提示

文章目录一、项目思路二、环境配置1.1、PaddlenHub模块&#xff08;飞桨预训练模型应用工具&#xff09;&#xff08;1&#xff09;预训练模型&#xff1a;pyramidbox_lite_mobile_mask&#xff08;2&#xff09;face_detection人脸检测模型&#xff08;默认为 pyramidbox_lite…

TCP的三次握手、四次挥手

文章目录前言一、一些重要字段的含义二、TCP总括图三、三次握手详细过程1.第一次握手2.第二次握手3.第三次握手三次握手小结4.为什么必须要进行三次握手&#xff0c;两次或四次就不行四、四次挥手1.第一次挥手2.第二次挥手3.第三次挥手4.第四次挥手四次挥手简述前言 一个TCP的…

MySQL中自带的数据库表相关介绍

mysql的自带数据库表主要有以下几个&#xff1a; &#xff08;1&#xff09;information_schema &#xff08;2&#xff09;performance_schema &#xff08;3&#xff09;mysql &#xff08;4&#xff09;sys &#xff08;5&#xff09;可能存在空数据库test 一、informa…

预告|2月25日 第四届OpenI/O 启智开发者大会昇腾人工智能应用专场邀您共启数字未来!

如今&#xff0c;人工智能早已脱离科幻小说中的虚构想象&#xff0c;成为可触及的现实&#xff0c;并渗透到我们的生活。随着人工智能的发展&#xff0c;我们正在迎来一个全新的时代——数智化时代。数据、信息和知识是这个时代的核心资源&#xff0c;而人工智能则是这些资源的…

【TensorFlow 】查看Tensorflow和python对应版本、将现有的TensorFlow更新到指定的版本

1、查看Tensorflow和python对应版本 1.1这里我是在TensorFlow官方网址产看的 1、打开官方网址 https://pypi.org/project/tensorflow/1.1.0rc2/#files但是这个网址好像打不开&#xff0c;点击会出现这样 问题不大 输入Tensorflow然后点击搜索&#xff0c;就会跳转到https://p…

[蓝桥杯 2015 省 B] 移动距离

蓝桥杯 2015 年省赛 B 组 H 题题目描述X 星球居民小区的楼房全是一样的&#xff0c;并且按矩阵样式排列。其楼房的编号为 1,2,3,⋯ 。当排满一行时&#xff0c;从下一行相邻的楼往反方向排号。比如&#xff1a;当小区排号宽度为 6 时&#xff0c;开始情形如下&#xff1a;我们的…

复旦发布国内首个类ChatGPT模型MOSS,和《流浪地球》有关?

昨晚&#xff0c;复旦大学自然语言处理实验室邱锡鹏教授团队发布国内首个类ChatGPT模型MOSS&#xff0c;现已发布至公开平台https://moss.fastnlp.top/ &#xff0c;邀公众参与内测。 MOSS和ChatGPT一样&#xff0c;开发的过程也包括自然语言模型的基座训练、理解人类意图的对…

XXL-JOB 分布式任务调度平台

1.什么是XXL-JOB XXL-JOB 是由国人许雪里开发的一个 开源的轻量级分布式任务调度平台。 学习简单、轻量级、易扩展&#xff0c;开箱即用&#xff0c;现已开放源代码并接入200多家公司线上产品线 2.XXL-JOB设计思想 a.将调度行为抽象为调度中心公共平台,而平台本身不承担业务…

[oeasy]python0089_大型机的衰落_Dec小型机崛起_PDP_VAX网络

编码进化 回忆上次内容 上次 回顾了 计算机存储单位的演变 最小的读写单位 是 bit 8-bit 固定下来 成为了字节(Byte) 位数容量8-bit1Byte1024Byte1 KB1024 KB1 MB1024 MB1 GB1024 GB1 TB 存储字符时 第1位 是 标志位后7位 是 ascii具体的值 可以用 1Byte 存储 计算机之间 …

Java:什么是异常?什么是异常处理?

Java中的异常处理不是一个容易的话题。初学者很难理解&#xff0c;即使是经验丰富的开发人员也可以花几个小时讨论应该如何抛出或处理哪些Java异常。这就是为什么大多数开发团队都有自己的一套关于如何使用它们的规则。如果你是一个团队的新手&#xff0c;你可能会惊讶于这些规…

HR:你会Python数据分析吗?

之前看到一个段子&#xff1a; 以前去面试&#xff0c;HR会问你“精通office吗&#xff1f;” 现在去面试&#xff0c;HR会问你“会Python数据分析吗&#xff1f;” 图片来源&#xff1a;网络 大数据时代&#xff0c;无论是数据分析师、研发&#xff0c;到运营、市场、产品经…

AI 在编程、写作、绘画领域的占卜:从 GitHub Copilot 到 ChatGPT,再到 Stable Diffusion...

PS&#xff1a;就当前节点&#xff08;2023.02.22&#xff09;而言&#xff0c;我虽然研究过一段时间传统的机器学习&#xff0c;但是并不擅长深度学习等领域&#xff0c;所以很多 AI 领域相关的词汇&#xff0c;我是不擅长的&#xff0c;只为自己总结一下&#xff0c;方便在未…

Android的NDK之编译LED的动态库so和使用so

文章目录 目录 文章目录 基础信息 我的AS基础信息 gradle插件版本 硬件信息 基础知识 externalNativeBuild 具体操作步骤 下载NDK库 加入JNI相关C语言代码和mk文件 local.properties里配置ndk路径 gradle配置 配置externalNativeBuild 打包动态库 引用第三方so库…

为什么要使用微服务架构?【微服务架构出现的背景】

随着互联网技术的发展&#xff0c;传统的应用架构已满足不了实际需求&#xff0c;微服务架构就随之产生。那么传统应用架构到底出了什么问题呢?又如何解决?接下来我们将从传统单体架构的问题开始&#xff0c;对为什么需要微服务架构进行详细讲解。传统单体应用架构的问题通常…

钓鱼网站+persistence植入后门程序+创建用户

本实验实现1&#xff1a; 利用MS14-064漏洞&#xff0c;会生成一个网址&#xff0c;诱导用户点击&#xff0c;打开后&#xff0c;会直接连接到发起攻击的主机上&#xff0c;即可攻击成功。 本实验实现2&#xff1a; 一旦入侵成功&#xff0c;则拿到控制目标主机的部分权限&…

春种一粒粟:企业如何修炼好云原生内功?

日月盈昃&#xff0c;辰宿列张。寒来暑往&#xff0c;秋收冬藏。《千字文》里蕴藏了一种人与天地之间共处的智慧&#xff0c;那就是想要收获粮食&#xff0c;一定要提前播种。农耕如此&#xff0c;百业如此&#xff0c;数字化创新也是一样。数字化技术&#xff0c;已经成为全球…

速来!掘金数据时代2022年度隐私计算评选活动火热报名中!

开放隐私计算 开放隐私计算开放隐私计算OpenMPC是国内第一个且影响力最大的隐私计算开放社区。社区秉承开放共享的精神&#xff0c;专注于隐私计算行业的研究与布道。社区致力于隐私计算技术的传播&#xff0c;愿成为中国 “隐私计算最后一公里的服务区”。183篇原创内容公众号…

一月券商金工精选

✦研报目录✦ ✦简述✦ 按发布时间排序 国盛证券 “薪火”量化分析系列研究&#xff08;二&#xff09;-票据逾期数据中的选股信息 发布日期&#xff1a;2023-01-04 关键词&#xff1a;股票、票据、票据预期 主要内容&#xff1a;本文深入探讨了“票据持续逾期名单”这一…

CentOS7.6 MySQL8安装

1 检查是否安装过 MySQL rpm -qa | grep -i mysqlmariadb rpm -qa | grep mariadb2 卸载之前的安装 MySQL rpm -e --nodeps 软件名 //强力删除&#xff0c;对相关依赖的文件也进行强力删除卸载 rpm -e --nodeps mariadb-libs-5.5.60-1.el7_5.x86_643 官网下载 MySQL :: D…

kettle简单使用-将CSV转换为Excel文件_操作过程---大数据之kettle工作笔记003

做个例子把csv转换为xls文件 可以看到过程 首先右键文件 新建 转换 然后选择核心对象 输入 CSV文件输入 然后选择输出excel输出,拖拽过来 然后按住shift 然后鼠标左键,然后拉出一条线到输出 然后松开的