c++可调用对象、function类模板与std::bind

news2025/2/25 11:44:18

函数调用与函数调用运算符

先写一个简单的函数,如下:

/*函数的定义*/
int func(int i)
{
	cout<<"这是一个函数\t"<<i<<endl;
}

void test()
{
	func(1);//函数的调用
}

通过这个普通的函数可以看到,调用一个函数很简单,首先是函数名,后面跟一对圆括号,如果这个函数有参数,就在函数调用位置的函数名后圆括号中给出实参,在函数定义位置函数名后圆括号中给出形参

函数调用总离不开一对圆括号,这个“()”就叫函数调用运算符

特别的,如果在类中重载了这个函数调用运算符(),我们就可以像使用函数一样使用类的对象,换句话说就可以像函数调用一样来“调用”该类的对象

这种重载了()的类对象,就叫做函数对象,如下


class biggerThanZero
{
public:
	int m_i;
	biggerThanZero(int i):m_i(i)
	{
		cout<<"构造函数调用了"<<endl;
	}

	int operator()(int num)
	{
		if(num<0) return 0;
		return num;
	}
};

void test()
{
	biggerThanZero bz1(10);//这是对象定义的初始化,会调用类的构造函数
	
	int num=-20;
	cout<<bz1(num)<<endl;//这才是调用了类的函数调用运算符()

}

        

分析代码并观察运行结果可以看到,测试函数中的第一行代码是一个正常的调用构造函数对类对象进行初始化的代码,而接下来的两行代码里的bz1(num)才是对函数调用运算符的调用,观察其书写形式可以发现,其调用形式与文章开头的普通函数func调用形式是相同的

这就是可调用对象,即

  • func函数
  • 重载了函数调用运算符“()”的biggerthanzero类所生成的对象bz1,我们调用bz1时可以像调用函数那样进行调用

问题的引出

继续观察这两个可调用对象,可以发现他们的调用形式都是相同的,也就是int (int),同时这也是func的函数类型

注:函数类型就是返回值类型和参数类型的组合。

如func函数中的参数类型是int,而返回值类型int,因此func的函数类型就是int (int)。

同理,假如现在有一个函数的声明为 void fun(double);则这个fun函数的函数类型就是

void (double)

而我们又知道,我们可以使用一个函数指针来保存函数地址,那么操作这个函数指针就等同于操作这个函数指针所指向的函数,如下所示

void test()
{
	int (*fp)(int);//定义一个函数指针,也就是可以保存函数地址的指针
	fp=func;//在c++中,函数名就是这个函数的地址,也可以使用fp=&func
	fp(1);//由于fp现在指向func,因此调用fp(1)就等同于调用func(1)
}

但是对于相同的调用形式的函数对象,我们却不能使用函数指针进行统一调用

void test()
{
	int (*fp)(int,int);
	biggerThanZero bz(0);//类对象初始化
	fp=bz;//报错
}

这说明系统并没有把类biggerthanzero的对象bz看成一个函数指针,当然系统更不可能把类biggerthanzero看成一个函数指针

那么问题来了,对于具有相同调用形式的不同可调用对象,是否可以使用一个统一的调用呢?

function类模板的引出

暂时先搁置一下上边提到的概念,我们暂时已经知道以下三点:

  1. 普通函数和重载了函数调用运算符()的对象都是可调用对象
  2. 可以使用函数指针统一调用普通函数这个可调用对象
  3. 但是同为可调用对象的函数对象却无法使用函数指针进行统一调用

接下来,我们做两个实验来引出function类模板

观察以下代码

int max(int a,int b)
{
	return (a>b)?a:b;
}

int min(int a,int b)
{
	return (a<b)?a:b;
}

void test()
{
	cout<<max(1,2)<<endl;
	cout<<min(1,2)<<endl;
}

在这段代码里,我们定义了两个函数,分别是max函数和min函数,并且我们在测试函数中调用了他们。

实验一

仔细观察这段代码,可以发现max函数和min函数的函数类型是相同的,都是int(int ,int ),因此我们可以使用一个函数指针来对他们进行统一形式的调用,如下

void test()
{
	int (*fp)(int,int);//定义一个函数指针
	fp=max;//将max函数的函数地址赋给函数指针fp
	cout<<fp(1,2)<<endl;//现在fp指向的是max函数,调用fp(1,2)等同于调用max(1,2)
	fp=min;//将min函数的函数地址赋给函数指针fp
	cout<<fp(1,2)<<endl;//现在fp指向的是min函数,调用fp(1,2)等同于调用min(1,2)
}

运行结果如下: 

 其实,这就是函数指针最开始的作用,但是后来面向对象诞生之后这种写法就被替代了。

实验二

再进一步,我们可以使用一个map来对这段逻辑进行重写,如下:

void test()
{
	map<string,int(*)(int,int)> mp;
	mp.insert({"max",max});
	mp.insert({"min",min});

	cout<<mp["max"](1,2)<<endl;//等同于调用max(1,2)
	cout<<mp["min"](1,2)<<endl;//等同于调用min(1,2)
}

 在上述代码里,我们使用一个map对上述调用形式进行了统一的改写,map键值对的键类型是string,而值类型是函数类型为int(int,int)的函数指针

综上观察,我们可以说

  • 同为可调用对象,且函数对象和普通函数的调用形式是相同的,但是我们只能使用函数指针对调用形式相同(函数类型名相同)的普通函数进行统一调用
  • 但是我们却无法再进一步对相同调用形式的函数对象进行统一调用

为了解决以上问题,c++引入了function模板的概念,我们直接看代码

int func(int i)
{
	cout<<"这是一个函数\t"<<i<<endl;
}

class biggerThanZero
{
public:
	int m_i;
	biggerThanZero(int i):m_i(i)
	{
		cout<<"构造函数调用了"<<endl;
	}

	int operator()(const int& num)
	{
		if(num<0) return 0;
		return num;
	}
};

void test()
{
	function<int(int)> fp2;
	fp2=func;
	cout<<fp2(10)<<endl;
	biggerThanZero bz(0);
	fp2=bz;
	cout<<fp2(-1)<<endl;
}

现在我们可以拿出function类模板的定义和作用了

function的类模板,就是用来包装一个可调用对象的,换句话说,它可以像函数指针那样,将不同的可调用对象包装成一个相同的东西,以方便我们使用

正如我们使用函数指针那样,对于同一个函数指针,我们可以赋给它不同的函数地址,只要这些函数的调用形式是相同的,我们就可以使用这一个指针实现不同的函数调用,而function类模板实现的就是类似的功能

现在,我们就可以进一步使用map来对可调用对象进行统一封装了

void test()
{
	biggerThanZero bz(0);
	map<string,function<int(int)>> mp={
		{"func",func},
		{"biggerThanZero",bz}
	};

	mp["func"](20);
	cout<<mp["biggerThanZero"](-10)<<endl;
}

因此,我们可以把函数、可调用对象等都包装成function<int(int)>对象。 

但是,值得说明的是,无法对重载的函数包装进function中

我们只能这样写

void test()
{
	function<int(int,int)> fc;
	int(*fp)(int,int)=max;
	fc=fp;
	cout<<fc(1,2)<<endl;
	
}

可调用对象

在上边我们已经说了有关可调用对象的概念,这里再强调一下,普通函数和重载了函数调用运算符的类对象(也就是函数对象)都是可调用对象,他们都可以像使用普通函数调用那样进行调用

除了上述两个可调用对象外,其余还有一些可调用对象,我们一并进行介绍

1.函数指针

普通函数的函数名就是一个函数地址,是一个可调用对象,我们已经介绍过了


void func()
{
	cout<<"func运行了"<<endl;
}

void test()
{
	void(*fp)()=func;//定义函数指针,并使用func的函数地址进行初始化
	fp();//调用函数,这是一个可调用对象
}

2.函数对象(仿函数)

具有operator()成员函数的类对象也是一个可调用对象,我们也已经介绍过了。在这里我们给出函数对象(仿函数)的定义

仿函数的定义:仿函数(functors)又称为函数对象(function objects),是一个能行使函数功能的类所定义的对象。仿函数的语法几乎和普通的函数调用一样

class TC
{
public:
	void operator()(int tv)
	{
		cout<<"TC::operator()执行了\t"<<tv<<endl;
	}
};

void test()
{
	TC tc;
	tc(10);//像调用函数那样调用()操作符,等价于tc.operator(10)
}

3.可转换为函数指针的类对象

其实,函数指针都是可调用对象,可以在代码中像函数一样使用

这在我们之前探讨函数指针的时候已经见到过了,当时我们定义了两个函数类型为int(int,int)的max函数与min函数,并使用同一个函数指针分别指向max函数和min函数的地址进行调用,使用函数指针进行调用的时候我们也观察到其调用与普通函数调用无异

现在我们说一种特殊的函数指针,如下

可转换为函数指针的类对象也叫做函数对象或者仿函数

在说这个问题之前,先说一下有关类型转换的问题

我们知道

  1. 一个类对象的类型就是定义这个对象的类本身,比如上边的对象tc的类型就是TC。
  2. 常用的普通类型之间是可以进行类型转换的,如int可以转换成double,而double可以转换成int
  3. 再进一步,我们又知道,我们可以将一个int类型的变量转换为一个类类型,只要这个类提供了对应的构造函数,如下:
    class TS
    {
    public:
    	int m_i;
    
    	TS(){
    		cout<<"无参构造函数调用了"<<endl;
    	};
    	TS(int a):m_i(a)
    	{
    		cout<<"带参数的构造函数调用了"<<endl;
    	}
    };
    
    
    void test()
    {
    	TS ts;
    	ts=10;//通过构造一个临时对象,将int类型的10转换为一个TS类型的变量
    }
    

  4. 当然,上述这种类型转换是隐式,而如果我们不希望编译器为我们使用这种int到类型之间的隐式类型转换,使用显示类型转换也是可以的,如下:
     

    class TS
    {
    public:
    	int m_i;
    
    	TS(){
    		cout<<"无参构造函数调用了"<<endl;
    	};
    	explicit TS(int a):m_i(a)//声明explicit,不允许编译器使用隐式类型转换
    	{
    		cout<<"带参数的构造函数调用了"<<endl;
    	}
    };
    
    
    void test()
    {
    	TS ts;
    	//由于构造函数使用了explicit关键字,不允许编译器使用隐式类型转换,因此我们只能使用显示类型转换
    	ts=TS(10);
    }

那么问题来了,是否可以将一个类类型转换为一个普通类型呢,如将上述的TS类类型转换成一个int类型?

答案当然也是肯定的。

类型转换运算符

类型转换运算符,也叫做类型转换函数,是类的一种特殊成员函数,它能将一个类类型对象转成某个其他类型数据

其一般形式为:

先说以下几点说明,我们再使用代码进行实验

  1. 末尾的const是可选项,表示不应该改变待转换对象的内容,但不是必须有const
  2. “类型名”表示要转换成的某种类型,一般只要是能作为函数返回类型的类型都可以。所以一般不可以转成数组类型或者函数类型(把一个函数声明去掉函数名剩余的部分就是函数类型,如void(inta,intb)),但是转换成数组指针、函数指针、引用等都是可以的
  3. 类型转换运算符,没有形参(形参列表必须为空),因为类型转换运算符是隐式执行的,所以无法给这些函数传递参数。同时,也不能指定返回类型,但是却会返回一个对应类型(“类型名”所指定的类型)的值
  4. 必须定义为类的成员函数

接下来,我们做实验以做验证

class TS
{
public:
	int m_i;

	TS(){
		cout<<"无参构造函数调用了"<<endl;
	};
	TS(int a):m_i(a)//声明explicit,不允许编译器使用隐式类型转换
	{
		cout<<"带参数的构造函数调用了"<<endl;
	}

	//类型转换运算符,必须定义为一个成员函数
	//表示编译器可以调用这个函数隐式的将TS类型的变量转换为一个int类型的变量
	operator int() const
	{
		return m_i;
	}
};


void test()
{
	TS ts1;//普通构造
	ts1=10;//调用带参构造,将int类型的10隐式转换为TS类型的变量
	int res=ts1+20;//隐式调用operator int()成员函数将对象ts1转换为int类型变量之后再做加法运算
	cout<<res<<endl;
	int res2=ts1.operator int()+30;//也可以显示的调用operator int()成员函数将对象ts1转换为int类型变量
	cout<<res2<<endl;
}

上述代码执行过程为

  1. 调用无参构造函数构造ts1对象
  2. 调用有参构造函数并生成一个临时对象,隐式的将int类型变量的10转换为一个TS类型变量,再把这个临时对象赋给ts1
  3. 隐式的调用operator int()成员函数将ts1转换为int类型,再进行加法运算,执行10+20
  4. 显示的调用operator int()成员函数将ts1转换为int类型,再进行加法运算,执行10+30

同样的,如果我们想拒绝隐式类型转换,也可以使用显示类型转换运算,如下:


class TS
{
public:
	int m_i;

	TS(){
		cout<<"无参构造函数调用了"<<endl;
	};
	TS(int a):m_i(a)//声明explicit,不允许编译器使用隐式类型转换
	{
		cout<<"带参数的构造函数调用了"<<endl;
	}

	//类型转换运算符,必须定义为一个成员函数
	//表示编译器可以调用这个函数隐式的将TS类型的变量转换为一个int类型的变量
	explicit operator int() const//拒绝编译器使用隐式类型转换
	{
		return m_i;
	}
};


void test()
{
	TS ts1;//普通构造
	ts1=10;//调用带参构造,将int类型的10隐式转换为TS类型的变量
	int res=static_cast<int>(ts1)+20;//显示类型转换
	cout<<res<<endl;
}

现在我们转回正题,我们现在已经知道了,类类型也可以转换为其他类型,如int类型,而类类型也同样可以转换为函数指针类型,而这种可以转换为函数指针的类对象,也是一种可调用对象 

为了说明问题,我们继续写一个测试代码

class TS
{
public:
	int m_i;

	TS(){
		cout<<"无参构造函数调用了"<<endl;
	};
	TS(int a):m_i(a)//声明explicit,不允许编译器使用隐式类型转换
	{
		cout<<"带参数的构造函数调用了"<<endl;
	}

	static void myfunc(int v1)
	{
		cout<<"static myfunc函数执行了\t"<<v1<<endl;
	}

	//类型定义,void(*)(int)是一个函数指针类型,使用tfpoint替换这个类型
	//等价于typedef void(*tfpoint)(int)
	using tfpoint=void(*)(int);
	operator tfpoint()
	{
		cout<<"类型转换运算符调用了"<<endl;
		return myfunc;
	}
};


void test()
{
	TS ts;
	//执行operator tfpoint(),之后再执行myfunc(123)
	ts(123);
}

4.lambda表达式

lambda表达式也是一种可调用对象,参见

lambda表达式c++-CSDN博客

可调用对象总结

其实,可调用对象首先被看作一个对象,程序员可以对其使用函数调用运算符“()”,那就可以称其为“可调用的”

function类模板

如果找通用性,上述提到的这几种可调用对象的调用形式都比较统一,那么,有没有什么方法能够把这些可调用对象的调用形式统一一下呢?有,那就是使用std::function把这些可调用对象包装起来。这在我们之前也已经实验过了

但有一点需要注意,function类模板用来往里装各种可调用对象,但是它不能装类成员函数指针,因为类成员函数指针是需要类对象参与才能完成调用的。

std::function类模板的特点是:通过指定模板参数,它能够用统一的方式来处理各种可调用对象。

1.绑定普通函数

void func1(int num)
{
	cout<<"这是一个普通函数func1:"<<num<<endl;
}

void test()
{
	function<void(int)> fp1=func1;//绑定一个普通函数
	func1(10);
}

2.绑定类的静态成员函数


class TC
{
public:
	static void stcfunc(int num)
	{
		cout<<"TC类的静态成员函数执行了:"<<num<<endl;
	}
};


void test()
{
	function<void(int)> fp2=TC::stcfunc;
	fp2(20);
}

3.绑定仿函数


class TC
{
public:
	int m_i;
	TC(int i=0):m_i(i)
	{
		cout<<"构造函数运行了"<<endl;
	}

	void operator()(int num)
	{
		cout<<"函数调用运算符运行了:"<<num<<endl;
	}

	static void stcfunc(int num)
	{
		cout<<"TC类的静态成员函数执行了:"<<num<<endl;
	}
};


void test()
{
// 	function<void(int)> fp1=func1;//绑定一个普通函数
// 	func1(10);

	function<void(int)> fp2=TC::stcfunc;
	fp2(20);

	TC tc;
	function<void(int)> fp3=tc;//TC类声明了函数调用运算符,因此对象tc是一个函数对象
	fp3(30);
}

std::bind绑定器

std::bind能将对象以及相关的参数绑定到一起,绑定完后可以直接调用,也可以用std::function进行保存,在需要的时候调用。

 std::bind有两个意思:·

  • 将可调用对象和参数绑定到一起,构成一个仿函数,所以可以直接调用。·
  • 如果函数有多个参数,可以绑定部分参数,其他的参数在调用的时候指定。

绑定普通函数

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

void func1(int x,int y,int z)
{
    cout<<"x="<<x<<",y="<<",z="<<z<<endl;
}

void test()
{
    function<void(int,int,int)> bf1=bind(func1,10,20,30);
    bf1(10,20,30);

    auto bf2=bind(func1,10,20,30);
    bf2();

}

int main()
{
    test();
    // system("pause");
    return 0;
}

参数占位符

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

void func1(int x,int y,int z)
{
    cout<<"x="<<x<<",y="<<y<<",z="<<z<<endl;
}

void test()
{
    //_1和_2分别表示func1的第一个参数和第二个参数暂时不定,需要自己传入
    auto bf2=bind(func1,_1,_2,30);
    bf2(10,20);

    auto bf3=bind(func1,_2,_1,30);
    bf3(10,20);
}

int main()
{
    test();
    // system("pause");
    return 0;
}

可以看到,分别调整参数占位符的位置后,输出的结果是不一样的 

绑定函数对象


class CQ
{
public:
    void operator()(int x,int y)
    {
        cout<<x<<"\t"<<y<<endl;
    }
};

void test()
{
    CQ cq;
    bind(cq,10,20)();

    bind(cq,_1,20)(5);
}

绑定类成员函数

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

void func1(int x,int y,int z)
{
    cout<<"x="<<x<<",y="<<y<<",z="<<z<<endl;
}

class CQ
{
public:
    int m_a=0;
    void operator()(int x,int y)
    {
        cout<<x<<"\t"<<y<<endl;
    }

    void classFunc(int num)
    {
        cout<<"this is a class func:"<<num<<endl;
        m_a=num;
    }
};

void test()
{
    CQ cq;
    // bind(cq,10,20)();

    // bind(cq,_1,20)(5);

    bind(&CQ::classFunc,cq,55)();
    cout<<"m_a="<<cq.m_a<<endl;

    bind(&CQ::classFunc,&cq,_1)(23);
    cout<<"m_a="<<cq.m_a<<endl;

}

int main()
{
    test();
    // system("pause");
    return 0;
}

 注意,上面的代码中

第一个std::bind的调用中,第二个参数cq会导致生成一个临时的CQ对象,std::bind是将该临时对象和相关的成员函数以及多个参数绑定到一起,后续对myfunpt成员函数的调用修改的是这个临时的CQ对象的m_a值,并不影响真实的cq对象的m_a值。

如果将std::bind的第二个参数cq前增加&,这样就不会导致生成一个临时的CQ对象,后续的myfunpt调用修改的就会是cq对象的m_a值。

这就是为什么第二个std::bind模板调用后m_a的值改变的原因

绑定类成员变量

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

void func1(int x,int y,int z)
{
    cout<<"x="<<x<<",y="<<y<<",z="<<z<<endl;
}

class CQ
{
public:
    int m_a;
    CQ(int num=0):m_a(num)
    {
        cout<<"构造函数调用了"<<endl;
    }

    CQ(const CQ& cq):m_a(cq.m_a)
    {
        cout<<"拷贝构造函数调用了"<<endl;
    }

    void operator()(int x,int y)
    {
        cout<<x<<"\t"<<y<<endl;
    }

    void classFunc(int num)
    {
        cout<<"this is a class func:"<<num<<endl;
        m_a=num;
    }
    virtual ~CQ()
    {
        cout<<"析构函数执行了"<<endl;
    }

};

void test()
{
    CQ cq;
    function<int&(void)> fp=bind(&CQ::m_a,cq);
    fp()=66;
    cout<<cq.m_a<<endl;
}

int main()
{
    test();
    // system("pause");
    return 0;
}

注意观察输出

把成员变量地址当函数一样绑定,绑定的结果放在std::function<int &(void)>里保存。换句话说,就是用一个可调用对象的方式来表示这个变量,bind这个能力还是比较神奇的。

重点分析一下代码行“bf7()=60;”,因为其上面的那行代码用了&cq,所以这里等价于cq.m_a=60。如果cq前不用&,发现会调用两次CQ类的拷贝构造函数。

为什么调用两次拷贝构造函数呢?

第一次是因为第一个参数为cq,所以利用cq产生一个临时的CQ对象

第二次是因为std::bind要返回一个CQ对象(确切地说是经过std::bind包装起来的CQ对象),所以要返回的这个CQ对象(仿函数)复制自这个临时CQ对象,但bind这行执行完毕后,临时CQ对象被释放,返回的这个CQ对象(仿函数)放到了bf7里。 

所以上述std::bind代码行中,一般都应该用&cq,否则最终会多调用两次拷贝构造函数和两次析构函数,用了&cq后这4次调用全省了,提高了程序运行效率。

void test()
{
    CQ cq;
    function<int&(void)> fp=bind(&CQ::m_a,&cq);
    fp()=66;
    cout<<cq.m_a<<endl;
}

总结

  1. 因为有了占位符(placeholder)这种概念,所以std::bind的使用就变得非常灵活。可以直接绑定函数的所有参数,也可以仅绑定部分参数。绑定部分参数时,就需要通过std::placeholders来决定bind所在位置的参数将会属于调用发生时的第几个参数。
  2. std::bind的思想实际上是一种延迟计算的思想,将可调用对象保存起来,然后在需要的时候再调用。
  3. std::function一般要绑定一个可调用对象,类成员函数不能被绑定。而std::bind更加强大,成员函数、成员变量等都能绑定。

现在通过std::function和std::bind的配合,所有的可调用对象都有了统一的操作方法。

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

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

相关文章

黑盒测试,软件测试中的军体拳

黑盒测试&#xff0c;软件测试中的军体拳 让我们来讲一个故事黑盒测试的测试方法黑盒测试的测试方法用于黑盒测试的工具&#xff1a;黑盒测试可以识别什么黑盒测试的特点&#xff1a;黑盒测试的优点&#xff1a; 五星上将麦克阿瑟曾经说过“如果把白盒测试比做六脉神剑&#xf…

vue实现 marquee(走马灯)

样式 代码 <div class"marquee-prompt"><div class"list-prompt" refboxPrompt><span v-for"item in listPrompt" :title"item" class"prompt">{{item}}</span></div> </div>data() {…

快速入门Torch构建自己的网络模型

真有用构建自己的网络模型 读前必看刚学完Alex网络感觉很厉害的样子&#xff0c;我也要搭建一个可以看着网络结构实现上面的代码你已经很强了&#xff0c;千万不要再想实现VGG等网络&#xff01;&#xff01;&#xff01;90%你能了解到的模型大佬早已实现好&#xff0c;直接调用…

【python】16.Python语言进阶

Python语言进阶 重要知识点 生成式&#xff08;推导式&#xff09;的用法 prices {AAPL: 191.88,GOOG: 1186.96,IBM: 149.24,ORCL: 48.44,ACN: 166.89,FB: 208.09,SYMC: 21.29 } # 用股票价格大于100元的股票构造一个新的字典 prices2 {key: value for key, value in prices…

推荐几个Github高星GoLang管理系统

在Web开发领域&#xff0c;Go语言&#xff08;Golang&#xff09;以其高效、简洁、高并发等特性逐渐成为许多开发者的首选语言。有许多优秀的Go语言Web后台管理系统&#xff0c;这些项目星星众多&#xff0c;提供了丰富的功能和良好的代码质量。本文将介绍一些GitHub高星的GoLa…

07-微服务getaway网关详解

一、初识网关 在微服务架构中&#xff0c;一个系统会被拆分为很多个微服务。那么作为客户端要如何去调用这么多的微服务呢&#xff1f;如果没有网关的存在&#xff0c;我们只能在客户端记录每个微服务的地址&#xff0c;然后分别去调用。这样的话会产生很多问题&#xff0c;例…

设计模式之依赖倒转原则

在软件开发的世界里&#xff0c;设计模式一直是提升代码质量、确保软件稳定性以及优化软件可维护性的重要工具。而在这其中&#xff0c;依赖倒转原则无疑是其中最具代表性的设计模式之一。那么&#xff0c;什么是依赖倒转原则&#xff1f;它又为何如此重要&#xff1f;让我们一…

鼎捷软件获评国家级智能制造“AAA级集成实施+AA级咨询设计”供应商

为贯彻落实《“十四五”智能制造发展规划》&#xff0c;健全智能制造系统解决方案供应商(以下简称“供应商”)分类分级体系&#xff0c;推动供应商规范有序发展&#xff0c;智能制造系统解决方案供应商联盟组织开展了供应商分类分级评定(第一批)工作&#xff0c;旨在遴选一批专…

Python--GIL(全局解释器锁)

在Python中&#xff0c;GIL&#xff08;全局解释器锁&#xff09;是一个非常重要的概念&#xff0c;它对Python的多线程编程有着深远的影响。GIL是Python解释器级别的锁&#xff0c;用于保证任何时刻只有一个线程在执行Python字节码。这意味着即使在多核处理器上&#xff0c;Py…

[HTML]Web前端开发技术13(HTML5、CSS3、JavaScript )横向二级导航菜单 Web页面设计实例——喵喵画网页

希望你开心&#xff0c;希望你健康&#xff0c;希望你幸福&#xff0c;希望你点赞&#xff01; 最后的最后&#xff0c;关注喵&#xff0c;关注喵&#xff0c;关注喵&#xff0c;佬佬会看到更多有趣的博客哦&#xff01;&#xff01;&#xff01; 喵喵喵&#xff0c;你对我真的…

离线数据仓库-关于增量和全量

数据同步策略 数据仓库同步策略概述一、数据的全量同步二、数据的增量同步三、数据同步策略的选择 数据仓库同步策略概述 应用系统所产生的业务数据是数据仓库的重要数据来源&#xff0c;我们需要每日定时从业务数据库中抽取数据&#xff0c;传输到数据仓库中&#xff0c;之后…

探索Redis特殊数据结构:Bitmaps(位图)在实际中的应用

一、概述 Redis官方提供了多种数据类型&#xff0c;除了常见的String、Hash、List、Set、zSet之外&#xff0c;还包括Stream、Geospatial、Bitmaps、Bitfields、Probabilistic&#xff08;HyperLogLog、Bloom filter、Cuckoo filter、t-digest、Top-K、Count-min sketch、Confi…

一文掌握SpringBoot注解之@Async知识文集(1)

&#x1f3c6;作者简介&#xff0c;普修罗双战士&#xff0c;一直追求不断学习和成长&#xff0c;在技术的道路上持续探索和实践。 &#x1f3c6;多年互联网行业从业经验&#xff0c;历任核心研发工程师&#xff0c;项目技术负责人。 &#x1f389;欢迎 &#x1f44d;点赞✍评论…

手把手教你搭建一个数据可视化看板

前言 俗话说的好&#xff0c;“字不如表&#xff0c;表不如图”、“有图有真相&#xff0c;一图胜千言”。 数据可视化就是用图的形式把基础数据直观&#xff0c;简洁的&#xff0c;高效的展示出来&#xff0c;今天为大家介绍一下如何使用葡萄城公司的嵌入式BI工具——Wyn商业…

Unity3d C#实现场景编辑/运行模式下3D模型XYZ轴混合一键排序功能(含源码工程)

前言 在部分场景搭建中需要整齐摆放一些物品&#xff08;如仓库中的货堆、货架等&#xff09;&#xff0c;因为有交互的操作在单个模型上&#xff0c;每次总是手动拖动模型操作起来也是繁琐和劳累。 在这背景下&#xff0c;我编写了一个在运行或者编辑状态下都可以进行一键排序…

Day12 C基础(指针进阶)

文章目录 指针修饰1.const 修饰2.void 大小端二级指针指针和数组1.指针和一维数组直接访问&#xff1a;间接访问&#xff1a; 2.指针和二维数组直接访问&#xff1a;间接访问&#xff1a; 数组指针 指针修饰 1.const 修饰 1)const int num 10; const int num 10;num 3; i…

【面试合集】说说微信小程序的实现原理?

面试官&#xff1a;说说微信小程序的实现原理&#xff1f; 一、背景 网页开发&#xff0c;渲染线程和脚本是互斥的&#xff0c;这也是为什么长时间的脚本运行可能会导致页面失去响应的原因&#xff0c;本质就是我们常说的 JS 是单线程的 而在小程序中&#xff0c;选择了 H…

Mac系统下,保姆级Jenkins自动化部署Android

一、Jenkins自动化部署 1、安装jenkins 官网&#xff1a;macOS Installers for Jenkins LTS 选择macOS brew install jenkins-lts 安装最新: brew install jenkins-lts 启动jenkins服务: brew services start jenkins-lts 重启jenkins服务: brew services restart jenkin…

YOLOv5改进系列(27)——添加SCConv注意力卷积(CVPR 2023|即插即用的高效卷积模块)

【YOLOv5改进系列】前期回顾&#xff1a; YOLOv5改进系列&#xff08;0&#xff09;——重要性能指标与训练结果评价及分析 YOLOv5改进系列&#xff08;1&#xff09;——添加SE注意力机制 YOLOv5改进系列&#xff08;2&#xff09;——添加CBAM注意力机制 YOLOv5改进系列&…

Netty-Netty源码分析

Netty线程模型图 Netty线程模型源码剖析图 Netty高并发高性能架构设计精髓 主从Reactor线程模型NIO多路复用非阻塞无锁串行化设计思想支持高性能序列化协议零拷贝(直接内存的使用)ByteBuf内存池设计灵活的TCP参数配置能力并发优化 无锁串行化设计思想 在大多数场景下&#…