从C向C++26——C++11(3)

一.可调用对象

1.可调用对象

可调用对象分为四类:

  • 是一个函数指针

    int print(int a, double b)
    {
        cout << a << b << endl;
        return 0;
    }
    // 定义函数指针
    int (*func)(int, double) = print;
    //使用using起别名来定义函数指针,注意把using起的别名当作一个类,使用时通过构造一个实例对象指向相应的函数
    using func_ptr = int (*)(int, double)
    
    

    知识点1:函数指针的格式,返回值类型 (*指针名)(参数类型列表)

    知识点2:使用uisngtypedef起别名

  • 是一个具有operator()成员函数的类对象(仿函数)

    #include <iostream>
    #include <string>
    #include <vector>
    using namespace std;
    
    struct Test
    {
        // ()操作符重载
        void operator()(string msg)
        {
            cout << "msg: " << msg << endl;
        }
    };
    
    int main(void)
    {
        Test t;
        t("我是要成为海贼王的男人!!!");	// 仿函数
        return 0;
    }
    
    

    知识点1:重载操作符()(仿函数)

  • 是一个可被转换为函数指针的类对象

    #include <iostream>
    #include <string>
    #include <vector>
    using namespace std;
    
    using func_ptr = void(*)(int, string);
    struct Test
    {
        static void print(int a, string b)
        {
            cout << "name: " << b << ", age: " << a << endl;
        }
    
        // 将类对象转换为函数指针
        operator func_ptr()
        {
            return print;
        }
    };
    
    int main(void)
    {
        Test t;
        // 对象转换为函数指针, 并调用
        t(19, "Monkey D. Luffy");
    
        return 0;
    }
    

    知识点1:operator不仅可以重载操作符,还可以表示类型转化的意思,这时候其语法为

    operator 目标类型() const;
    

    因为这里的目标类型是一个函数指针,所以返回一个函数。

    注意点1:这里并不是重载()而构成的仿函数注意区分。

  • 是一个类成员函数指针或者类成员指针

    #include <iostream>
    #include <string>
    #include <vector>
    using namespace std;
    
    struct Test
    {
        void print(int a, string b)
        {
            cout << "name: " << b << ", age: " << a << endl;
        }
        int m_num;
    };
    
    int main(void)
    {
        // 定义类成员函数指针指向类成员函数
        void (Test::*func_ptr)(int, string) = &Test::print;           //这里的&可有可无,因为函数名本身就是地址
        // 类成员指针指向类成员变量
        int Test::*obj_ptr = &Test::m_num;
    
        Test t;
        // 通过类成员函数指针调用类成员函数
        (t.*func_ptr)(19, "Monkey D. Luffy");
        // 通过类成员指针初始化类成员变量
        t.*obj_ptr = 1;
        cout << "number is: " << t.m_num << endl;
    
        return 0;
    }
    

    知识点1:类成员函数指针或者类成员指针

2.包装器

std::function是可调用对象的包装器。它是一个类模板,可以容纳除了类成员(函数)指针之外的所有可调用对象。通过指定它的模板参数,它可以用统一的方式处理函数、函数对象、函数指针,并允许保存和延迟执行它们。

std::function必须要包含一个叫做functional的头文件,可调用对象包装器使用语法如下:

#include <functional>
std::function<返回值类型(参数类型列表)> diy_name = 可调用对象;

详细用法:

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

int add(int a, int b)
{
    cout << a << " + " << b << " = " << a + b << endl;
    return a + b;
}

class T1
{
public:
    static int sub(int a, int b)
    {
        cout << a << " - " << b << " = " << a - b << endl;
        return a - b;
    }
};

class T2
{
public:
    int operator()(int a, int b)
    {
        cout << a << " * " << b << " = " << a * b << endl;
        return a * b;
    }
};

int main(void)
{
    // 绑定一个普通函数
    function<int(int, int)> f1 = add;
    // 绑定以静态类成员函数
    function<int(int, int)> f2 = T1::sub;
    // 绑定一个仿函数
    T2 t;
    function<int(int, int)> f3 = t;

    // 函数调用
    f1(9, 3);
    f2(9, 3);
    f3(9, 3);

    return 0;
}

在上面的类对象转化为函数指针后,也可以利用包装器进行封装。

3.回调函数

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

class A
{
public:
    // 构造函数参数是一个包装器对象
    A(const function<void()>& f) : callback(f)
    {
    }

    void notify()
    {
        callback(); // 调用通过构造函数得到的函数指针
    }
private:
    function<void()> callback;
};

class B
{
public:
    void operator()()
    {
        cout << "我是要成为海贼王的男人!!!" << endl;
    }
};
int main(void)
{
    B b;
    A a(b); // 仿函数通过包装器对象进行包装
    a.notify();

    return 0;
}

callback 是一个 std::function<void()> 类型的对象,这意味着它可以存储任何没有参数且返回类型为 void 的可调用对象。在类 A 的构造函数中,callback 被初始化为传入的 function<void()> 参数,这样它就可以在之后被调用。

std::function 的这种能力使得它非常适合用于回调机制,因为它提供了一种统一的方式来处理不同的可调用对象。在您的例子中,B 类定义了一个重载了 () 运算符的成员函数,这使得 B 的对象可以像函数一样被调用,成为一个仿函数(functor)。当您创建 A 类的对象 a 并将 B 类的对象 b 作为参数传递给 A 的构造函数时,b 被自动转换为 std::function<void()> 类型,并在 A 类的 notify 成员函数中被调用。

4.绑定器

std::bind用来将可调用对象与其参数一起进行绑定。绑定后的结果可以使用std::function进行保存,并延迟调用到任何我们需要的时候。通俗来讲,它主要有两大作用:

  • 将可调用对象与其参数一起绑定成一个仿函数。
  • 将多元(参数个数为n,n>1)可调用对象转换为一元或者(n-1)元可调用对象,即只绑定部分参数。
// 绑定非类成员函数/变量
auto f = std::bind(可调用对象地址, 绑定的参数/占位符);
// 绑定类成员函/变量
auto f = std::bind(类函数/成员地址, 类实例对象地址, 绑定的参数/占位符);

注意这里bind函数得到的是一个可调用对象的包装器类型。

绑定器函数使用例子如下:

  • 基础:绑定后作为仿函数使用
#include <iostream>
#include <functional>
using namespace std;

void output(int x, int y)
{
    cout << x << " " << y << endl;
}

int main(void)
{
    // 使用绑定器绑定可调用对象和参数, 并调用得到的仿函数
    bind(output, 1, 2)();
    bind(output, placeholders::_1, 2)(10);
    bind(output, 2, placeholders::_1)(10);

    // error, 调用时没有第二个参数
    // bind(output, 2, placeholders::_2)(10);
    // 调用时第一个参数10被吞掉了,没有被使用
    bind(output, 2, placeholders::_2)(10, 20);

    bind(output, placeholders::_1, placeholders::_2)(10, 20);
    bind(output, placeholders::_2, placeholders::_1)(10, 20);


    return 0;
}

  • 绑定全局函数

    #include <iostream>
    #include <functional>
    using namespace std;
    
    void callFunc(int x, const function<void(int)>& f)
    {
        if (x % 2 == 0)
        {
            f(x);
        }
    }
    
    void output(int x)
    {
        cout << x << " ";
    }
    
    void output_add(int x)
    {
        cout << x + 10 << " ";
    }
    
    int main(void)
    {
        // 使用绑定器绑定可调用对象和参数
        auto f1 = bind(output, placeholders::_1);
        for (int i = 0; i < 10; ++i)
        {
            callFunc(i, f1);
        }
        cout << endl;
    
        auto f2 = bind(output_add, placeholders::_1);
        for (int i = 0; i < 10; ++i)
        {
            callFunc(i, f2);
        }
        cout << endl;
    
        return 0;
    }
    
  • 绑定成员函数(变量)

    #include <iostream>
    #include <functional>
    using namespace std;
    
    class Test
    {
    public:
        void output(int x, int y)
        {
            cout << "x: " << x << ", y: " << y << endl;
        }
        int m_number = 100;
    };
    
    int main(void)
    {
        Test t;
        // 绑定类成员函数
        function<void(int, int)> f1 = 
            bind(&Test::output, &t, placeholders::_1, placeholders::_2);
        // 绑定类成员变量(公共)
        function<int&(void)> f2 = bind(&Test::m_number, &t);
    
        // 调用
        f1(520, 1314);
        f2() = 2333;
        cout << "t.m_number: " << t.m_number << endl;
    
        return 0;
    }
    

二.模板补充

前提:已经了解基础的函数模板和类模板

1.嵌套模板

#include <iostream>

using namespace std;

template<class T>
class Data
{
public:
	Data(T data) :data(data) {}
private:
	T data;
};

template<class Ty>
class MM
{
public:
	MM(Ty data):data(data){}
	void print()
	{
		cout << data << endl;
	}
private:
	Ty data;
};

int main()
{
	MM<Data<string>> girl(Data<string>("string"));
	girl.print();

	return 0;
}

核心知识点:

  • 友元函数一般在类外定义,类内声明
  • 友元函数如果是一个模板函数,那么不仅类外定义时需要使用templaye修饰,而且类内声明时也需要使用templaye修饰
  • 每使用一个类(只要是类模板)都要配合<>使用
  • 嵌套时,最重要的一个问题时缺乏与之匹配的操作符(==》运算符重载)

上述代码肯定是错的,因为<<并没有进行重载

#include <iostream>

using namespace std;

template<class T>
class Data
{
public:
	Data(T data) :data(data) {}
	template <class T>
	friend ostream& operator<<(ostream& out, const Data<T>& object);
private:
	T data;
};

template<class Ty>
class MM
{
public:
	MM(Ty data):data(data){}
	void print()
	{
		cout << data << endl;
	}
private:
	Ty data;
};

template <class T>
ostream& operator<<(ostream& out, const Data<T>& object)
{
	out << object.data;
	return out;
}

int main()
{
	MM<Data<string>> girl(Data<string>("string"));
	girl.print();

	return 0;
}

2.可变参的模板函数

与原来的模板函数相比,它有一个折叠参数:template <class...Args>

展开折叠参数的过程:

  • 1:递归的方式展开参数包

    #include <iostream>
    
    template <class T>
    void print(const T& t) {
        std::cout << t << std::endl;
    }
    
    // 递归模板函数
    template <class T, class... Args>
    void printdata(const T& first, const Args&... rest) {
        print(first); // 打印第一个参数
        printdata(rest...); // 递归调用,处理剩余的参数
    }
    
    // 递归终止条件
    template <>
    void printdata() {
        // 当没有参数时,递归终止
    }
    
    int main() {
        printdata(1, 2.3, "hello");
        return 0;
    }
    

    在 C++ 中,当您看到一个函数模板的参数列表中包含 const T& first, const Args&... rest,这表示该函数模板接受至少一个参数,并且可以接受任意数量的额外参数。这里的 const T& first 是第一个参数,它是一个常量引用,而 const Args&... rest 是一个参数包,它包含剩余的所有参数,这些参数也是常量引用。

  • 2:列表的方式展开参数包

    #include <iostream>
    
    using namespace std;
    
    template<class T>
    void print(T data)
    {
    	cout << data << "\t";
    }
    
    template <class...Args>                                       //涉及到逗号表达式
    void printdata(Args...args)
    {
    	int array[] = { (print(args),0)... };                    
        //对args的每一个参数都执行print函数,每一次都返回0把它存储在array中
        //其中{}是初始化列表符号,重要的是理解内部的逗号表达式
    }
    
    int main()
    {
    	printdata(1, "hello word", 2.2f, 'A');
    
    	return 0;
    }
    
    

简单来谈一下我的理解:class...Args代表模板参数包(类),而Args...args实际上就相当于argsArgs的一个实例化,完全可以Args...x来实例一个叫x的函数参数包。

这里,(print(args), 0) 是一个逗号表达式,它首先对每个 args 参数调用 print 函数,然后返回 0。这个表达式被用在初始化列表中,列表中的 ... 是初始化列表展开运算符,它将模式 (print(args), 0) 应用到参数包 args 中的每个参数上。结果是创建了一个数组,其每个元素都是 0,数组的大小与 args 参数包中的参数数量相同。

3.逗号表达式

逗号表达式(Comma operator)是一个二元运算符,它用来序列化多个表达式。逗号表达式的值是它的右操作数的值。当逗号表达式作为语句出现时,它的左操作数会被计算,但它的结果会被丢弃,然后计算右操作数,并返回其结果。

expression1, expression2

理解:会从左到右执行每个语句,但是取最后一个语句expression2作为其返回值。

因此,{ (print(args), 0)... } 实际上是将 (print(args), 0) 这个逗号表达式应用到参数包 args 中的每个参数上,并为数组 array 初始化一个元素为 0 的序列。这个序列的长度与 args 参数包中的参数数量相同。...表示应用到数据包上。

4.tuple元组

在C++11后,标准库新添加了一种数据容器——tuple元组,tuple 的用法类似于 pair,但它可以包含超过两个元素。你可以创建和访问元组,以及使用 std::get 函数模板来获取元组中的元素。此外,tuple 提供了一些辅助函数,如 std::make_tuple 来创建元组,std::tie 来解包元组,以及 std::forward_as_tuple 来创建一个元组,其元素是通过完美转发得到的。

下面是一个简单的 tuple 用法示例:

#include <iostream>
#include <tuple>
#include <string>

int main() {
    // 创建一个元组
    std::tuple<int, std::string, double> tup(42, "Hello", 3.14);

    // 访问元组中的元素
    std::cout << "Integer: " << std::get<0>(tup) << std::endl;
    std::cout << "String: " << std::get<1>(tup) << std::endl;
    std::cout << "Double: " << std::get<2>(tup) << std::endl;
    
    // 访问元组中的第一个元素(整数)
    int i = std::get<0>(tup);
    std::cout << "Integer: " << i << std::endl;

    // 使用 std::tie 解包元组
    int i;
    std::string s;
    double d;
    std::tie(i, s, d) = tup;
    std::cout << "Unpacked Integer: " << i << std::endl;
    std::cout << "Unpacked String: " << s << std::endl;
    std::cout << "Unpacked Double: " << d << std::endl;

    return 0;
}

6.特化模板

在 C++ 中,template <> 是用来特化一个模板的语法。当您看到一个模板声明后面跟着 template <>,这表示您正在为一个特定的模板实例提供一个专门的实现,这个实现会覆盖默认的模板实例化。

特化模板通常用于以下几种情况:

  1. 递归终止条件:在处理可变参数模板时,您可能需要一个递归终止条件来结束递归。这通常是通过为一个没有参数的模板提供一个特化来实现。例如:
template <class... Args>
void printdata(Args... args) {
    // 处理参数
    printdata(args...); // 递归调用
}

template <>
void printdata() {
    // 递归终止条件,没有更多参数可处理
}

在这个例子中,printdata 是一个可变参数模板函数,它递归地调用自身来处理参数。当没有更多的参数需要处理时,它会调用一个特化的 printdata 版本,这个版本没有参数,作为递归终止条件。

2.特定类型的特殊行为:有时您可能需要为特定类型提供特殊的行为。例如,您可能需要一个模板函数来处理任何类型的数组,但对于 std::string 类型,您想要特殊处理。您可以这样做:

template <class T>
void processArray(T& arr) {
    // 默认处理
}

template <>
void processArray<std::string>(std::string& arr) {
    // 特殊处理 std::string
}

在这个例子中,processArray 是一个模板函数,它有一个针对 std::string 类型的特化版本,这个版本会提供特殊的行为。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

墨城烟柳ベ旧人殇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值