C++ [模板]

news2024/11/15 16:00:52

 

fdb6ecdd46d648d68c12fde40c3545aa.jpeg

     

  本文已收录至《C++语言》专栏!
作者:ARMCSKGT

        

007a4b2ff4c64e5496222622ce88fa38.gif


目录

前言

正文 

泛型编程

问题引入

泛型

 函数模板

概念

格式

使用方式

模板原理

模板的实例化

隐式实例化

显示实例化

模板匹配规则

类模板

类模板定义格式

类模板的实例化

非类型模板参数

模板的特化

概念

函数模板的特化

类模板的特化

关于类模板的特化

仿函数的定义和使用

模板分离编译

什么是分离编译

模板的分离编译

解决方案

模板总结 

优点

缺点

最后


 

前言

 

C++为了提高开发效率,提高代码的复用性增加了模板的概念;模板就像打印卷子一样,100份卷子只需要一个模板卷子就能打印出来,但模板的神奇不止于此,通过这篇文章,我将为您打开泛型编程的大门!

421df94ee05f1cb07cc3e47ac959c100.gif


正文 


C++的模板语法是根据泛型编程而来的,为了提高代码的复用性!


泛型编程


问题引入

 

在C语言中,我们要实现一个swap交换函数,只能应付一种数据类型,且C语言没有函数重载,对于这种场景及其乏力。

 

而在C++中有函数重载,但是除了内置类型还有无数的自定义类型,如果每一个类型都写一个swap函数,那么代码的重复性就太高了。

//int类型的swap
void Swap(int& left, int& right)
{
	int temp = left;
	left = right;
	right = temp;
}
//double类型的swap
void Swap(double& left, double& right)
{
	double temp = left;
	left = right;
	right = temp;
}
//char类型的swap
void Swap(char& left, char& right)
{
	char temp = left;
	left = right;
	right = temp;
}
......

使用函数重载虽然可以实现,但是有一下几个不好的地方:

  • 重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数
  • 代码的可维护性比较低,一个出错可能所有的重载均出错

泛型

 

那能否告诉编译器一个模子,让编译器根据不同的类型利用该模子来生成代码呢?

980d556eaa1e412eb27379b17af35c49.png

如果在C++中,也能够存在这样一个模具,通过给这个模具中填充不同材料(类型),来获得不同材料的铸件(即生成具体类型的代码),那将会节省许多头发。巧的是前人早已将树栽好,我们只需在此乘凉。



泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。

0b35f165f53f4433b7d6868322af2d0f.png


 函数模板


概念

函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。


格式

  • 关键字:template
  • 模板参数定义:template<typename T>或template<class T>
  • 注意:typename是用来定义模板参数关键字,也可以使用class(切记:不能使用struct代替class)
  • T是模板参数名,可以自定义模板参数名,typename和class可以在一个模板中定义参数(注意:只能使用这两个关键字定义,struct等其他关键字无法定义模板参数),多个模板参数之间通过逗号分隔!
  • 一个模板的所有的模板参数只能被一个类或函数使用
//示例
template<typename 模板参数1, typename 模板参数2,......,typename 模板参数n>
template<class 模板参数1, class 模板参数2,......,class 模板参数n>
template<typename 模板参数1, class 模板参数2,......>

使用方式

 

//在使用前定义好模板参数和模板函数
template<class T> //定义模板参数T
void Print(T data) 
{   //typeid(T).name() 以字符串的形式输出T的数据类型
    cout << typeid(T).name() << " : " << data << endl;
    //cout << typeid(data).name() << " : " << data << endl;的显示结果也是一样的
}

f632dd72944c41a582a089472b9a739d.png


模板原理


函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器!

24c879ba31ea4d10bf2a419cf61130c2.png

在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码,对于字符类型也是如此!

   

所以对于上面的示例,当我们调用模板函数时,编译器在编译时通过识别不同的数据类型实例化出对应类型的该函数,从而实现了不同类型调用同一个函数,底层上这些函数构成函数重载,但实际上我们以为只有一个函数,其实是编译器帮我们写了这些类型的函数!

1f7fc6b5058d4cdcb506f4830ec5eb9d.png

44c46864988e4159ab4f2dc147ad3125.png

Linux环境下,发现其汇编指令中调用了四个不同的函数(call是调用函数)!


模板的实例化


用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化。


隐式实例化

 

隐式实例化是让编译器通过参数自动确定参数类型从而形成对应的函数但是隐式实例化也可能存在一些问题!

#include <iostream>
using namespace std;

template<class T>
T Add(T left,T right)
{
    return left+right;
}

int main()
{
    cout<<Add(1,2)<<endl; //该处正常编译
    cout<<Add(1.0,2)<<endl; //此处发生异常
    return 0;
}

f519911903d246d3b653936e0cdb58f5.png

main函数中第二句不能通过编译,因为在编译期间,当编译器看到该实例化时,需要推演其实参类型通过实参left将T推演为double,通过实参right将T推演为int类型,但模板参数列表中只有一个T, 编译器无法确定此处到底该将T确定为 int 或者 double 类型而报错注意:在模板中,编译器一般不会进行类型转换操作,因为一旦转化出问题,编译器就需要背黑锅 Add(left, right);

  

这里的细节:根据编译器函数名生成规则,当编译器识别到1.0时,将T确定为double,此时生成的函数名是_3Adddd,而实际调用时是_3Adddi,此时发生链接错误,编译器直接报错!

    

如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器将会报错。


这种情况下有两种解决办法:

  • 1.强制类型转换
  • 2.定义多个模板参数
  • 3.显示实例化

    

1.强制类型转换

#include <iostream>
using namespace std;

template<class T>
T Add(T left,T right)
{
    return left+right;
}

int main()
{
    cout<<Add((int)1.0,2)<<endl; //对1.0进行强制类型转换
    cout<<Add(1.0,(double)2)<<endl; //对2进行强制类型转换
    return 0;
}

1e978921a5cf45568037b092c8cd6724.png

注意:在这里因为强制类型转换会产生临时变量,所以在传参时选择进行值传递,如果要使用引用传递,则需要加上const!

   

2.定义多个模板参数

#include <iostream>
using namespcae std;

template<class T1,class T2> //定义两个模板参数T1,T2
T2 Add(T1 left, T2 right) //返回值类型只能是模板参数中的其中一个
{
    return left + right;
}

int main()
{
    cout << Add(1.0, 2) << endl; 
    cout << Add(1.0, 2.2) << endl; 
    return 0;
}

c3d290597aac4170a0439dcb28d4f2ae.png

对于定义多个模板参数,如果函数有返回值,则只能由一个模板参数做返回值!

  

 3.显示实例化

对于显示实例化,需要展开详细介绍!


显示实例化

使用方法

#include <iostream>
using namespace std;

template<class T>
T Add(T left, T right)
{
    cout << typeid(T).name() << endl;
    return left + right;
}

int main()
{
    cout<<Add<int>(1.0,2)<<endl; //显示实例化为int类型的函数
    cout<<Add<double>(1.0,2)<<endl; //显示实例化为double类型的函数
    return 0;
}

74d18f009d464a7d8b52bb455d98aabc.png

 格式

模板函数名<类型>(参数); //将模板函数实例化为指定参数类型
模板类<类型> 实例对象; //实例化模板类为指定参数类型(后面会介绍类模板)

这里我们认识了新的符号 < > 用于实例化模板!

我们显示实例化的模板函数支持隐式类型转换,但是编译器推演的模板函数不支持隐式类型转换!


模板匹配规则


1.一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。因为无论是模板函数和显示定义的函数都构成重载,所以编译器会选择最匹配的那个函数进行调用,如果我们直接实现的函数是最佳选择则编译器不会实例化对应的模板函数!

#include <iostream>
using namespcae std;

template<class T>
T Add(T left, T right)
{
    cout << typeid(T).name() << endl;
    return left + right;
}

double Add(double left, int right) //再次显示定义Add函数
{
    cout << "_3Adddi" << endl;
    return left + right;
}

int main()
{
    cout << Add(1.0, 2) << endl; //显示实例化为int类型的函数
    cout << Add<int>(1.1, 2.2) << endl; //如果我们显示实例化则编译器会调用模板函数
    cout << Add(1, 2.2) << endl; //否则会调用其他较为匹配的函数
    return 0;
}

cc6a08945cc84e8f85f1fc62e21bfec7.png


2. 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模 板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板!

#include <iostream>
using namespace std;

template<class T>
T Add(T left, T right)
{
    cout << typeid(T).name() << endl;
    return left + right;
}

double Add(double left, double right)
{
    cout << "_3Adddd" << endl;
    return left + right;
}

int main()
{
    cout << Add(1.2, 2.3) << endl;
    cout << Add(1, 2) << endl;
    return 0;
}

88e078d9ae6e4def9e429898e7c2ccda.png


 3. 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换


类模板


类模板定义格式

#include <iostream>
using namespace std;

//template<class T1,class T2,...,class Tn> //模板参数列表
template<class T> 
class Test 
{
public:
    Test() {}
    Test(T n)
        :_num(n)
    {}

    void print()
    {
        cout << typeid(T).name() << endl;
        cout << _num << endl;
    }

private:
    T _num;
};

类模板的实例化

前面我们简单介绍了模板显示实例化,对于类模板实例化与函数模板实例化不同,函数模板可以通过参数推到,但类模板只能显示实例化,类模板实例化需要在类模板名字后跟 < > ,然后将实例化的类型放在 < > 中即可,类模板名字不是真正的类,而实例化的对象才是真正的类。

//对于上面的类模板
int main()
{
    Test<int> i(1); //其中Test是类名,Test<int>才是类型
    Test<char> c('a');
    Test<double> d(3.14);
    i.print();
    c.print();
    d.print();
    return 0;
}

9edadce9d3e14209b106f88c6bbf5bc2.png


关于类模板的一些注意事项

  • 模板类中的函数在定义时,如果没有在类域中,就需要通过 类模板+ 类域访问 的方式定义
  • 类模板不支持声明与定义分离(头文件声明,其他文件定义),会导致链接错误!

非类型模板参数


模板参数分类类型形参与非类型形参。

   

类型形参:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。

   

非类型形参:就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用,这个常量类似于函数参数也可以给缺省值,但是这个参数一旦实例化就不可修改。

// 定义一个模板类型的静态数组
template<class T, size_t N = 10>//这里N相当于给了缺省值10,也可以传递其他整数作为参数
class _array
{
public:
	_array()
		:_size(N)
	{}

	T& operator[](size_t index) { return _arr[index]; }
	const T& operator[](size_t index)const { return _arr[index]; }

	size_t size()const { return _size; }
	bool empty()const { return 0 == _size; }

private:
	T _arr[N];
	size_t _size;
};

//这个是我们自己实现的,在库中确实存在array这个容器,有兴趣的小伙伴可以了解了解

60f837d2114d4832a1e1f11028425e08.png

 注意

  • 浮点数、类对象以及字符串是不允许作为非类型模板参数的
  • 非类型的模板参数必须在编译期就能确认结果

模板的特化


 特化必须建立在有原模板的基础上!


概念

  

通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理,比如:实现了一个专门用来进行相加的函数模板。

class Date //日期类
{
public:
	Date() = default;
	Date(int y, int m, int d)
		:_y(y)
		, _m(m)
		, _d(d)
	{}

	bool operator<(const Date& d)
	{
		return _y < d._y
			|| _y == d._y && _m < d._m
			|| _y == d._y && _m == d._m && _d < d._d;
	}

	bool operator>(const Date& d)
	{
		return !(*this < d);
	}

	int _y;
	int _m;
	int _d;
};

template<class T>
bool Less(T left, T right)
{
	return left < right;
}

int main()
{
	cout << Less(1, 2) << endl; // 可以比较,结果正确
	Date d1(2022, 7, 7);
	Date d2(2022, 7, 8);
	cout << Less(d1, d2) << endl; // 可以比较,结果正确
	Date* p1 = &d1;
	Date* p2 = &d2;
	cout << Less(p1, p2) << endl; // 可以比较,结果错误
	return 0;
}

e710582deb0c4e5eb43d527361054903.png

显然编译器内置的<运算符无法对指针所指的内容进行比较!

 

此时,就需要对模板进行特化。即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。模板特化中分为函数模板特化与类模板特化。


函数模板的特化

  

函数模板的特化步骤:

  1. 必须要先有一个基础的函数模板
  2. 关键字template后面接一对空的尖括号<>
  3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
  4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误

对以上代码中的指针特殊情况进行特化

// 对Less函数模板进行特化
template<>
bool Less<Date*>(Date* left, Date* right)
{
	return *left < *right; //解引用转而调用Date自己的运算符重载
}

int main()
{
	Date d1(2022, 7, 7);
	Date d2(2022, 7, 8);
	Date* p1 = &d1;
	Date* p2 = &d2;
	cout << Less(p1, p2) << endl; // 可以比较,结果错误
	return 0;
}

584cf2a7ea1148eba338b854b24032be.png

注意:一般情况下如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该函数直接接出。

bool Less(Date* left, Date* right)
{
    return *left < *right;
}

该种实现简单明了,代码的可读性高,容易书写,因为对于一些参数类型复杂的函数模板,特化时特别给 出,因此函数模板不建议特化。


类模板的特化

1.全特化

全特化即是将模板参数列表中所有的参数都确定化。

#include <iostream>
using namespace std;

template<class T1, class T2>
class Test
{
public:
	Test() { cout << "Data<T1, T2>" << endl; }
private:
	T1 _d1;
	T2 _d2;
};

template<> //全特化
class Test<int, char>
{
public:
	Test() { cout << "Data<int, char>" << endl; }
private:
	int _d1;
	char _d2;
};

int main()
{
	Test<int, int> d1;
	Test<int, char> d2;
	return 0;
}

20d6823a2e384cf481001ec2668fae7e.png

   

2.偏特化

偏特化:任何针对模版参数进一步进行条件限制设计的特化版本。比如对于以下模板类:

template<class T1, class T2>
class Test
{
public:
	Test() { cout << "Data<T1, T2>" << endl; }
private:
	T1 _d1;
	T2 _d2;
};

偏特化有以下两种表现方式:

  • 部分特化

将模板参数类表中的一部分参数特化

//对于上面的Test类
//将第二个参数特化为int
template <class T1>
class Test<T1, int>
{
public:
	Test() { cout << "Data<T1, int>" << endl; }
private:
	T1 _d1;
	int _d2;
};
  • 参数更进一步的限制

偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本。

//两个参数偏特化为指针类型
template <typename T1, typename T2>
class Test <T1*, T2*>
{
public:
	Test() { cout << "Data<T1*, T2*>" << endl; }

private:

	T1 _d1;
	T2 _d2;
};

//两个参数偏特化为引用类型
template <typename T1, typename T2>
class Test <T1&, T2&>
{
public:
	Test(const T1& d1, const T2& d2)
		: _d1(d1)
		, _d2(d2)
	{
		cout << "Data<T1&, T2&>" << endl;
	}

private:
	const T1& _d1;
	const T2& _d2;
};

int main()
{
	Test<double, int> t1; // 调用特化的int版本
	Test<int, double> t2; // 调用基础的模板 
	Test<int*, int*> t3; // 调用特化的指针版本
	Test<int&, int&> t4(1, 2); // 调用特化的指针版本
	return 0;
}

8e33faf81404497798d0b87feb77b8c5.png


关于类模板的特化

#include <iostream>
using namespace std;
#include<vector> //vector容器 - 这里大家可以当成顺序表理解即可
#include <algorithm>

class Date //日期类
{
public:
	Date() = default;
	Date(int y, int m, int d)
		:_y(y)
		, _m(m)
		, _d(d)
	{}

	bool operator<(const Date& d)
	{
		return _y < d._y
			|| _y == d._y && _m < d._m
			|| _y == d._y && _m == d._m && _d < d._d;
	}

	bool operator>(const Date& d)
	{
		return !(*this < d);
	}

	int _y;
	int _m;
	int _d;
};


template<class T> //仿函数实现比较
struct Less
{
	bool operator()(const T& x, const T& y) const
	{
		return x < y;
	}
};

int main()
{
	Date d1(2022, 7, 7);
	Date d2(2022, 7, 6);
	Date d3(2022, 7, 8);
	vector<Date> v1;
	v1.push_back(d1);
	v1.push_back(d2);
	v1.push_back(d3);

	// 可以直接排序,结果是日期升序
	sort(v1.begin(), v1.end(), Less<Date>());
	vector<Date*> v2;
	v2.push_back(&d1);
	v2.push_back(&d2);
	v2.push_back(&d3);

	// 可以直接排序,结果错误日期还不是升序,而v2中放的地址是升序
	// 此处需要在排序过程中,让sort比较v2中存放地址指向的日期对象
	// 但是走Less模板,sort在排序时实际比较的是v2中指针的地址,因此无法达到预期
	sort(v2.begin(), v2.end(), Less<Date*>());
	return 0;
}

通过观察上述程序的结果发现,对于日期对象可以直接排序,并且结果是正确的。但是如果待排序元素是指 针,结果就不一定正确。因为:sort最终按照Less模板中方式比较,所以只会比较指针,而不是比较指针指 向空间中内容,此时可以使用类版本特化来处理上述问题:

// 对Less类模板按照指针方式特化
template<>
struct Less<Date*>
{
	bool operator()(Date* x, Date* y) const
	{
		return *x < *y;
	}
};

特化之后,在运行上述代码,就可以得到正确的结果!


只有类模板有全特化和偏特化的概念,函数模板只有特化的概念!


仿函数的定义和使用

   

//定义一个相加的仿函数
template<class T> //可以使用模板参数也可以指定仿函数的参数类型
struct Add //类对象
{
    T operator()(const T& left, const T& right) //重载 () 自定义函数功能
    {
        return left + right;
    }
};

int main()
{
    cout << Add<int>()(1, 2) << endl; //通过匿名对象调用仿函数
    cout << Add<double>()(1.1, 2.2) << endl;
    return 0;
}

fbb0afa086334a05bcf5c69f74ca7397.png

仿函数是通过重载运算符 () 实现的,在一些涉及比较大小的函数例如sort排序和部分STL容器中会使用,通常less是小,greater是大,在使用时进行实例化就行了,之所以会有仿函数这样的语法,是因为C语言对于将函数作为参数传递十分不友好,C++使用仿函数传递参数极大的方便了函数作为参数进行传递的不足!


对于仿函数的一些细节,在后续会进行介绍!


模板分离编译


什么是分离编译

一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。


模板的分离编译

 假如有以下场景,模板的声明与定义分离开,在头文件中进行声明,源文件中完成定义:

// test.h
template<class T>
T Add(const T & left, const T & right);
// test.cpp
template<class T>
T Add(const T & left, const T & right)
{
	return left + right;
}

// main.cpp
#include"test.h"
int main()
{
	Add(1, 2);
	Add(1.0, 2.0);

	return 0;
}

分析:

8ef38288a1b74e0580b878b61e5056b5.png


解决方案

1. 将声明和定义(实现)放到一个文件 "xxx.hpp" 里面或者"xxx.h"其实也是可以的(推荐使用这种)。

2. 模板定义的位置显式实例化;这种方法不实用,不推荐使用。

  

声明和定义放在一起,直接就可以实例化,编译时就有地址,不需要链接(去其他文件中找),而且如果是在对象中,对于短小的函数还可能会被编译器修饰为内联函数直接展开!


模板总结 


优点

  1. 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生
  2. 增强了代码的灵活性

缺点

  1. 模板会导致代码膨胀问题(编译器生成过多的模板函数),也会导致编译时间变长
  2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误

最后

<C++ 模板>的介绍到这里就差不多结束了,相信模板的到来一定可以解决大家困惑已久因为类型而导致代码重复的问题,合理的使用模板可以极大的提高我们的生成效率和代码复用,而且模板会贯穿我们C++学习的始终,包括后面STL容器就是通过模板实现了所有类型的兼容,所以对于模板的掌握是必不可少的!

本次 <++ 模板> 就先介绍到这里啦,希望能够尽可能帮助到大家。

如果文章中有瑕疵,还请各位大佬细心点评和留言,我将立即修补错误,谢谢!

35edba3b72a0ab22f56bc7f5b0722a85.jpeg

🌟其他文章阅读推荐🌟

C++ <内存管理> -CSDN博客

C++ <类和对象 - 下> -CSDN博客

 C++ <类和对象 - 中> -CSDN博客 

C++ <类和对象 - 上> -CSDN博客

🌹欢迎读者多多浏览多多支持!🌹

 

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

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

相关文章

【13 Listener 学习笔记】

Listener 笔记记录 1. Listener监听器2. 监听对象的监听器2.1 ServletContetListener2.2 HttpSessionListener2.3 ServletRequestListener 3. 监听域对象属性变化的监听器3.1 ServletContextAttributeListener3.2 HttpSessionAttributeListener3.3 ServletRequestAttributeList…

每日一个小技巧:如何去水印而不损图片?赶紧学起来

在数码时代中&#xff0c;照片的处理与分享已经成为了我们日常中不可或缺的一部分。但是&#xff0c;大家在网上保存的图片常常会带有水印&#xff0c;非常影响图片的观赏性。水印旨在防止照片被盗用或侵权&#xff0c;但有时候它也很破坏照片的美感&#xff0c;因此许多人都在…

超级实用的C++学习网站

重要说明&#xff1a;该博客长期更新&#xff0c;方便读者查阅&#xff01; 一、参考资料 学习C这几个网站足矣 二、C学习网站 C中文网 cppreference 当之无愧的C学习第一网站。该网站希望给程序员提供一个关于C和C的完整的在线参考&#xff0c;所以它的内容非常的丰富。有…

动态类型语言、静态类型语言、强类型语言、弱类型语言解释

首先要明确这些名词都是针对数据类型展开的各自定义&#xff0c;同样针对数据类型在编译时和运行时会有一些限定或者规则存在。动态类型语言不能等同于弱类型语言&#xff0c;静态类型语言也不能等同于强类型语言。 静态类型语言和动态类型语言放到一个维度来进行评价类型系统&…

ClickHouse物化视图

目录 1 概述1.1 物化视图与普通视图的区别1.2 优缺点1.3 基本语法 2 案例实操2.1 准备测试用表和数据2.2 创建物化视图2.3 导入增量数据2.4 导入历史数据 1 概述 ClickHouse 的物化视图是一种查询结果的持久化&#xff0c;它确实是给我们带来了查询效率的提升。用户查起来跟表没…

MYSQL---主从同步概述与配置

一、MYSQL主从同步概述 1、什么是MySQL主从同步&#xff1f; 实现数据自动同步的服务结构 主服务器(master): 接受客户端访问连接 从服务器(slave)&#xff1a;自动同步主服务器数据 2、主从同步原理 Maste&#xff1a;启用binlog 日志 Slave&#xff1a;Slave_IO: 复制master主…

CPU寄存器的分类与Intel 8086 的eax,ebx,ecx,edx

目录 一、CPU中的寄存器分类 1.用户可见寄存器 2.控制和状态寄存器 一、CPU中的寄存器分类 大致分为两类: 一类属于用户可见寄存器&#xff0c;对这类寄存器编程&#xff0c;以及通过优化使CPU因使用这类寄存器&#xff0c;而减少对主存的访问次数&#xff0c; 另一类属于控…

MPRC086444-005对其进行维护和管理,以确保系统的稳定性和可靠性。

​ MPRC086444-005对其进行维护和管理&#xff0c;以确保系统的稳定性和可靠性。 变电站自动化系统优缺点 变电站自动化系统结构 变电站自动化系统优缺点 变电站自动化系统是以计算机技术、自动控制技术及通信技术为核心&#xff0c;对变电站及配电系统各个环节进行自动化控制和…

自动化生成持久化游戏管理器

自动化生成持久化游戏管理器 引言游戏管理器持久化自动化生成游戏管理器Addressables 引言 自动化生成的持久化游戏管理器是一个指通过使用自动化工具和技术来生成游戏的持久化管理器的过程。持久化管理器是负责管理游戏状态的组件&#xff0c;包括存储和检索游戏数据的功能&a…

GPT3 和它的 In-Context Learning

作者 | 太子长琴 整理 | NewBeeNLP 大家好&#xff0c;这里是NewBeeNLP。ChatGPT 的爆火让很多 NLPer 大吃一惊&#xff0c;焦虑感爆棚&#xff0c;它的思路和方法都不复杂&#xff0c;但效果却出奇的好。 我想任何研究成果的爆发都不可能是一蹴而就的&#xff0c;期间必然包含…

【历史上的今天】4 月 18 日:第一款交互式电子游戏;IBM 率先研发兆位芯片;硬件公司 Roland 成立

整理 | 王启隆 透过「历史上的今天」&#xff0c;从过去看未来&#xff0c;从现在亦可以改变未来。 今天是 2023 年 4 月 18 日&#xff0c;在 1955 年的今天&#xff0c;著名物理学家爱因斯坦在美国新泽西州的普林斯顿逝世。爱因斯坦于 1905 年获得物理学博士学位&#xff0c…

vue---双向绑定

目录 1、.sync修饰符-双向绑定 2、v-model修饰符-双向绑定 3、双向绑定原理 vue 中的双向绑定是语法糖。 1、.sync修饰符-双向绑定 . vue是单向数据流的。父组件可以通过prop向子组件传递数据。子组件需要通过自定义事件来将自己的数据变更通知给父组件&#xff0c;我们可以通过…

分布式任务调度系统分析

背景介绍 首先&#xff0c;我们来思考一些几个业务场景&#xff1a; XX 信用卡中心&#xff0c;每月 28 日凌晨 1:00 到 3:00 需要完成全网用户当月的费用清单的生成XX 电商平台&#xff0c;需要每天上午 9:00 开始向会员推送送优惠券使用提醒XX 公司&#xff0c;需要定时执行…

多线程并发编程学习笔记9(小滴课堂)------线程池及Executor框架

它只会使用10个线程。因为我们设置了它的容量。 我们现在把这个队列容量设置为20. 我们可以看到这里它使用了20个线程。但是出了异常&#xff0c;这个后面我们会学习。 我们现在使用一下我们的callable&#xff1a; 一般我们如果是想在线程执行完以后&#xff0c;获得一个返回…

019 - C++ 中的局部静态(local static)

在前几期里&#xff0c;我们了解了static关键字在特定上下文中的含义。 今天我们看一看另一个环境。我们可以在局部作用域中使用 static 来声明一个变量。 这种情况和我们之前看到的两种static有点不同。这次的局部静态 Local static 有更多的含义。 声明一个变量&#xff0…

个人知识库(持续更新中)

打造一个属于自己的知识库 为什么会有这个知识库会记录什么内容基础知识Java核心Java WebMySQL 中间件&工具项目代码资源仿牛客社区Web开发华夏ERP软件 视频资源代码之外持续更新中… 为什么会有这个知识库 作为羊哥的死忠粉&#xff0c;当他谈到个人知识库这个东西的时候…

RS-485 基础知识:何时需要端接,以及如何正确端接

RS-485 网络的许多信号完整性和通信问题都源于端接&#xff0c;这可能是因为缺少端接或端接不正确。在 RS-485 基础知识系列的这一部分&#xff0c;我将讨论何时不需要端接 RS-485 网络&#xff0c;以及在需要端接时如何使用标准&#xff08;并联&#xff09;端接和交流电 (AC)…

【JavaEE】常见的锁策略与CAS的ABA问题

文章目录 1 常见的锁策略1.1 乐观锁与悲观锁1.2 轻量级锁与重量级锁1.3 自旋锁与挂起等待锁1.4 互斥锁与读写锁1.5 可重入锁与不可重入锁1.6 公平锁与非公平锁 2 CAS 操作2.1 CAS 简介2.2 CAS 的应用2.2.1 实现原子类2.2.2 实现自旋锁 3 CAS 的 ABA 问题写在最后 1 常见的锁策略…

Nacos 客户端的服务发现与服务订阅机制的纠缠 - 篇七

Nacos 客户端的服务发现与服务订阅机制的纠缠 - 篇七 历史篇章 &#x1f550;Nacos 客户端服务注册源码分析-篇一 &#x1f551;Nacos 客户端服务注册源码分析-篇二 &#x1f552;Nacos 客户端服务注册源码分析-篇三 &#x1f553;Nacos 服务端服务注册源码分析-篇四 &am…

最新入河排污口设置论证、水质影响预测与模拟、污水处理工艺分析及建设项目入河排污口方案报告书

随着水资源开发利用量不断增大&#xff0c;全国废污水排放量与日俱增&#xff0c;部分河段已远远超出水域纳污能力。近年来,部分沿岸入河排污口设置不合理&#xff0c;超标排污、未经同意私设排污口等问题逐步显现&#xff0c;已威胁到供水安全、水环境安全和水生态安全&#x…