当前位置: 首页 > news >正文

泛型编程 之模板(template)

    C++另一种编程思想称为 泛型编程,主要利用的技术就是模板


        

目录

    C++另一种编程思想称为 泛型编程,主要利用的技术就是模板

          一、概念

          二、函数模板 

        1、语法与使用:

        2、函数模板注意事项

        3、普通函数与函数模板的区别

        4、普通函数与函数模板的调用规则

        5、模板的局限性

         三、类模板

作用:建立一个通用类,类中的成员属性的数据类型可以不具体指定,用一个虚拟的类型来表示

1、语法与使用

 2、类模板与函数模板的区别

 3、类模板中成员函数创建时机

4、类模板对象作函数参数

三种方式:

①指定传入的类型: 直接显示对象的数据类型(主要用的类型)

②参数模板化:        将对象中的参数变为模板进行传递

③整个类 模板化:   将这个对象类型 模板化进行传递

 5、类模板与继承

①当子类继承的父类是一个类模板时,子类在声明时,要指定出父类中T的类型

②如果不指定,编译器无法给子类分配内存,因为不知道父类中成员的大小

③如果想灵活指出父类中T的类型,子类也需要变成类模板

6、类模板成员函数类外实现

①类模板构造函数类外实现

②类模板成员函数类外实现

7、类模板分文件编写

①直接包含.cpp文件

②将声明与实现写在一个hpp文件中(约定俗成hpp,并不必须)

8、类模板与友元

①全局函数类内实现:直接在类内声明友元即可

②全局函数类外实现:先提取让编译器知道全局函数的存在

 9、类模板案例

 实现一个通用的数组类,要求:

①可以对内置数据类型以及自定义数据类型进行存储

②将数组中的数据存储到堆区

③构造函数中要传入数组的容量

④提供拷贝构造函数以及operator=,以防止浅拷贝问题

⑤提供尾插法与尾删法对数组中数据进行增加与删除

⑥通过下标的方式访问数组中的元素

⑦获取当前数组中元素的个数以及数组的容量


        一、概念

建立通用的模具,提高复用性

拍一寸照片的模板:

 制作PPT的模板

但是注意:模板并不是万能的,如人的1寸照片模板,不能用来拍其他生物的照片

而且,制作PPT时不能直接使用,需要自己加内容啥的


因此:

①模板不能直接使用,只是一个框架,需要自己合理使用

②模板是通用的,但不是万能的 


          二、函数模板 

作用:建立一个通用函数,其返回值类型与形参类型可以不指定,用一个虚拟的类型来代表


        1、语法与使用:

template<typename T>
函数声明或定义

template:声明创建模板

typename:表示其后面的符号是一种数据类型,可以用class

T:通用的数据类型,名称可以替换,通常为大写字母T


接下来使用交换函数swap来举例:

首先写出int整型交换函数float浮点型交换函数

// 整型 交换函数
void swapInt(int& x, int& y)
{int tmp = y;y = x;x = tmp;
}
// 浮点型 交换函数
void swapFloat(float& x, float& y)
{float tmp = y;y = x;x = tmp;
}

测试:

int main()
{int a = 10;int b = 20;swapInt(a, b);cout << a <<" " << b << endl;float c = 10.5;float d = 20.5;swapFloat(c, d);cout << c << " " << d << endl;return 0;
}

 可以正常交换并输出


但是,想要交换不同的数据,就必须实现不同的函数,改变返回值类型或者形参类型,但是具体代码又类似

因此,我们使用函数模板

template<typename T>
void Myswap(T& x, T& y)
{T tmp = y;y = x;x = tmp;
}

调用Myswap函数

	int a = 10;int b = 20;Myswap(a, b);cout << a <<" " << b << endl;float c = 10.5;float d = 20.5;Myswap(c, d);cout << c << " " << d << endl;

而我们在调用函数时,并未告知Myswap函数我们传入的类型是什么,使编译器自动推导出类型并实现

这叫做 自动推导类型


还有个 显式指定类型

	int e = 100;int f = 20;Myswap<int>(e, f);cout << e << " " << f << endl;

 就是调用函数时,在参数列表前加上<int>,这就相当于告知编译器刚才的类型T等于int


       2、函数模板注意事项

①自动推导类型,必须推导出一致的数据类型T,才可以正常使用

以我们上面的交换函数Myswap举例:

template<typename T>
void Myswap1(T& x)
{T tmp = y;y = x;x = tmp;
}

我们写2个不同的数据类型,尝试调用

	int a = 10;float b = 22.4;Myswap1(a, b);

直接报错

 因此不能使用

不过,如果在函数模板里写2个typename,则可以正常调用

template<typename T,typename Y>
void Myswap1(T& x, Y& y)
{Y tmp = y;y = x;x = tmp;
}

2个数据类型

	int a = 10;float b = 22.4;Myswap1(a, b);

 此时可以正常输出,但是输出结果都是int整型

②模板必须要确定T的数据类型,才可以使用

template<typename T>
void test01()
{cout << "ko" << endl;
}

此时函数内未声明T的数据类型

尝试调用

直接报错

因为未确定T的函数类型


解决:

在函数调用参数列表前加上<int>,即随便指定个数据类型,因为函数中没有使用具体的类型

 


实例1:

使用函数模板实现选择排序不同数据类型的数组进行降序排序

首先使用模板实现选择排序

template<typename T>
void Select_sort(T arr[],int len)
{int index = 0;for (int i = 0; i < len - 1; i++){index = i;for (int j = i + 1; j < len; j++){    // 我们认定的最大值比数组中某个值小if (arr[index] < arr[j]){   // 交换两者下标index = j;}}if (index != i) // 如果交换了下标(不等于原来的i){MYswap(arr[index], arr[i]);//进行数组中数据的交换}}
}

最后一步有数据的交换,我们接着使用模板实现交换函数

template<typename T>
void MYswap(T& x, T& y)
{T tmp = x;x = y;y = tmp;
}

这里就完成了,不过为了使数组中内容可以呈现在屏幕上,我再额外实现一个打印函数

template<typename T>
void print(T arr, int len)
{for (int i = 0; i < len; i++){cout << arr[i] << " ";}cout << endl;
}

①int类型数组测试

void int_test()
{// 整型数组测试int arr[] = { 2,4,6,1,3,26,7,1,2,4,9,0,2,3 };int len = sizeof(arr) / sizeof(arr[0]);print(arr, len);Select_sort(arr, len);cout << endl;print(arr, len);
}

②char类型数组测试

void char_test()
{// 字符数组测试char arr2[] = "zcnasijda";int len2 = sizeof(arr2) / sizeof(char);print(arr2, len2);Select_sort(arr2, len2);cout << endl;print(arr2, len2);
}

成功进行降序排序 


        3、普通函数与函数模板的区别

普通函数调用时,可以发生自动类型转换,(隐式类型转换

函数模板调用时,如果使用自动类型推导,则不会发生隐式类型转换

函数模板调用时,如果使用显式指定类型,则可以发生隐式类型转换


首先创建普通函数

// 普通函数
void add01(int x, int y)
{cout << x + y << endl;
}

调用:

	add01(10, 20);

 创建字符变量c并调用

	char c = 'c';add01(10, c);

 因为c的ASCII码值是99,99+10=109,函数在内部进行隐式类型转换

下面用函数模板实现

template<typename T>
void add02(T x, T y)
{cout << x + y << endl;
}

使用自动类型推导调用10与字符c

 直接报错,因为不能进行隐式类型转换

而我们使用显式指定类型:

 可以正常输出,因为进行了隐式类型转换,无论你传入什么数据,都给你转成int,转不成就报错


        4、普通函数与函数模板的调用规则

①如果普通函数与函数模板都可以实现,优先调用普通函数

实现一个大致相同普通函数与函数模板

void print(int x, int y)
{cout << "普通函数" << endl;
}
template<typename T>
void print(T x, T y)
{cout << "函数模板" << endl;
}

调用生成

② 在情况①下,通过空模板参数列表,可以强制调用函数模板

 即在函数调用的参数列表前加<>

③函数模板也可以发送重载

template<typename T>
void print(T x, T y)
{cout << "函数模板" << endl;
}
template<typename T>
void print(T x, T y,T z) // 重载
{cout << "函数模板" << endl;
}

④如果函数模板可以发生更好的匹配,优先调用函数模板

 2个char类型的变量,传入普通函数需要进行隐式类型转换,而传入函数模板只需要进行自动类型推导,因此优先调用函数模板


        5、模板的局限性

局限性:模板的通用并不万能

举例:写一个判断变量是否相等的模板,以及相等输出相等,否则输出不等的函数

template<typename T>
bool My_compare(T a, T b)
{if (a == b){return true;}else{return false;}
}
void judge(bool a)
{if (a){cout << "相等" << endl;}else{cout << "不等" << endl;}
}

测试:

void test01()
{int a = 10;int b = 10;bool ret = My_compare(a, b);judge(ret);
}

很明显,a==b

 但是,如果a和b的类型是一个类class

class person
{
public:person(string name,int age){m_age = age;m_name = name;}int m_age;string m_name;
};void test02()
{person p1("Tim", 22);person p2("Tim", 22);bool ret = My_compare(p1, p2);judge(ret);
}

很明显,p1==p2,但是编译器无法正常运行

 因为person是自定义数据类型,编译器不知道咋办


因此,我们使用具体化person的版本实现代码,具体化优先调用

在下面重写一个模板,前面加template<>,后面数据类型写person

template<>bool My_compare(person& p1, person& p2)
{if (p1.m_age == p2.m_age && p1.m_name == p2.m_name){return true;}else{return false;}
}

再次尝试运行

void test02()
{person p1("Tim", 22);person p2("Tim", 22);bool ret = My_compare(p1, p2);judge(ret);
}

成功,而传入数组判断相等也是同样的问题


总结:

①利用具体化的模板,可以解决自定义类型的通用化

②学习模板不是为了写模板,而是为了在STL能够运用系统提供的模板


三、类模板

作用:建立一个通用类,类中的成员属性的数据类型可以不具体指定,用一个虚拟的类型来表示


1、语法与使用

template<class T>
类

与函数模板基本相同,只要把typename改成class就行,而且二者可以互换,效果相同


例:写一个类模板,其中有属性name与age,不指定类型,调用输出

template<class Name_type,class Age_type >
class person
{
public:person(Name_type name, Age_type age){m_name = name;m_age = age;}void show(){cout << this->m_name << this->m_age << endl;}Name_type m_name;Age_type m_age;
};

测试:

void test01()
{person<string, int> p1("Joyce", 22);p1.show();
}


 2、类模板与函数模板的区别

①类模板没有自动类型推导使用方式

对于刚才的类模板

template<class Name_type,class Age_type >
class person
{
public:person(Name_type name, Age_type age){m_name = name;m_age = age;}void show(){cout << this->m_name << this->m_age << endl;}Name_type m_name;Age_type m_age;
};

如果我们使用自动类型推导:

直接报错

 而只能用显式指定类型

②类模板在模板参数列表中可以有默认参数

我们在模板的参数列表中加入默认参数

 让Age_type直接等于int

void test02()
{person<string> p2("tatina", 22); // 显式指定类型p2.show();
}

 在调用时,我们不用写其类型也可以正常运行


 3、类模板中成员函数创建时机

普通类中:成员函数一开始就可创建

类模板中:成员函数在调用时才创建


例:

首先创建2个类,区分为类1与类2,内部分别创建函数输出数字1与2

class person1
{
public:void show1(){cout << "1" << endl;}
};
class person2
{
public:void show2(){cout << "2" << endl;}
};

下面实现类模板

template<class T>
class Myclass
{
public:T obj;void m_show1(){obj.show1();}void m_show2(){obj.show2();}
};

测试:参数列表传person1,调用m_show1函数

void test02()
{Myclass<person1> m;m.m_show1();
}

输出1


尝试调用2:

 报错

此时就已经说明类模板中成员函数一开始没有创建,只有在调用了,才能确定对象的类型,才能创建成员函数

而我们类型传入person2,运行结果相反


4、类模板对象作函数参数

三种方式:

①指定传入的类型: 直接显示对象的数据类型(日常主要用的类型)

②参数模板化:        将对象中的参数变为模板进行传递

③整个类 模板化:   将这个对象类型 模板化进行传递


创建一个类模板参数类型为T1与T2

template<class T1,class T2>
class person
{
public:person(T1 name, T2 age){m_name = name;m_age = age;}void showinfo(){cout << this->m_name << this->m_age << endl;}T1 m_name;T2 m_age;
};

①指定传入的类型:

void print1(person<string, int>& p)// 直接把下面的类型拿来使用
{p.showinfo();
}
void test01()
{person<string, int> p1("joyce", 21);print1(p1);
}

可见,print1函数参数直接拿p1的数据类型使用,这就是指定传入的类型


②参数模板化:

void test02()
{person<string, int> p2("tatina", 20);print2(p2);
}

print2函数

template<class T1, class T2> 
void print2(person<T1, T2>& p2)// 参树模板化为T1与T2
{p2.showinfo();cout << typeid(T1).name() << endl;// 查看T1的类型cout << typeid(T2).name() << endl;// 查看T2的类型
}

就是将参数也模板化为T1与T2

同时,如果想查看数据的类型,可以使用typeid().name()函数


③整个类模板化

 同样的测试函数

void test03()
{person<string, int> p3("yomi", 1);print3(p3);
}

然后是print3,将整个类模板化

template<class T>
void print3(T& p)// 直接用T,让编译器推导出类型
{p.showinfo();
}

看一下T的类型


 5、类模板与继承

注意:

①当子类继承的父类是一个类模板时,子类在声明时,要指定出父类中T的类型

②如果不指定,编译器无法给子类分配内存,因为不知道父类中成员的大小

③如果想灵活指出父类中T的类型,子类也需要变成类模板


首先,创建父类base类模板

template<class T>
class base
{
public:T m_a;
};

尝试创建子类

class son :public base
{;
};

直接报错:

需要指定父类中T的类型

 

 这样就可以

不过此时,父类中的T只能是指定的T类型,为了解决这个问题:

②将子类也类模板

template<class T1,class T2>
class son2 :public base<T2>
{T1 m_a;
};
void test02()
{son2<string,int> s2; // 显式指定类型
}

string就是T1,int就是T2

T2就是继承父类base,并指定父类中T的类型为int


接着我们输出T1与T2的类型

 


6、类模板成员函数类外实现

掌握类模板中的成员函数类外实现


首先,我们写一个常规的person类模板,其中有属性m_name与m_age,类型分别为T1与T2

带有构造函数与void show函数

template<class T1,class T2>
class person
{
public:person(T1 name,T2 age){this->m_name = name;this->m_age = age;}void show(){cout << this->m_name << " " << this->m_age << endl;}T1 m_name;T2 m_age;
};

①类模板构造函数类外实现

接下来,我们先将类模板中的构造函数的实现部分屏蔽只留下声明,在类模板实现构造函数

template<class T1,class T2>
person<T1, T2>::person(T1 name, T2 age)
{this->m_name = name;this->m_age = age;
}

即在构造函数person(T1 name, T2 age)前加上作用域person::以及参数列表<T1,T2>

②类模板成员函数类外实现

template<class T1,class T2>
void person<T1,T2>::show() // 虽未用到T1T2,但由于其是类模板中成员函数,因此仍要写入T1/T2
{cout << this->m_name << " " << this->m_age << endl;
}

一样的加作用域与参数列表


测试:

void test01()
{person<string, int> p("joyce",21);p.show();
}


7、类模板分文件编写

首先,分文件编写类模板时(类模板.h头文件,其中的成员函数在.cpp源文件实现),成员函数创建时机是在调用阶段,导致编写时链接不到


先创建person.h头文件,其中写入person类模板

template<class T1, class T2>
class person
{
public:person(T1 name, T2 age);void show();T1 m_name;T2 m_age;
};

然后创建person.cpp源文件,其中实现person的函数,并包含.h头文件

// 构造函数
template<class T1, class T2>
person<T1, T2>::person(T1 name, T2 age)
{this->m_name = name;this->m_age = age;
}
// 成员函数
template<class T1, class T2>
void person<T1, T2>::show()
{cout << this->m_name << " " << this->m_age << endl;
}

到目前为止,代码运行没有任何问题

 但是,我们在测试函数中调用构造函数与成员函数

#include"person.h"void test01()
{person<string, int> p("joyce",21);p.show();
}

直接报错 ,因为编译器无法链接类模板成员函数的实现cpp文件


①直接包含.cpp文件

我们将源文件包含的person.h文件改为person.cpp文件,这样编译器直接链接到了cpp文件与h文件 

成功实现


②将声明与实现写在一个hpp文件中(约定俗成名为hpp,并非必须)

 上面是声明,下面是类外实现

 在源文件中包含

成功运行 


8、类模板与友元

2种实现:

①全局函数类内实现:直接在类内声明友元即可

②全局函数类外实现:先提前让编译器知道全局函数的存在


首先,写一个person类模板,其中有属性m_age与m_name,以及构造函数person

template<class T1, class T2>
class person
{
public:person(T1 name, T2 age){this->m_name = name;this->m_age = age;}private:T1 m_name;T2 m_age;
};

①全局函数类内实现

接下来,在其中加上友元的全局函数

	// 全局函数 类内实现friend void print1(person <T1, T2>p){cout << p.m_name << " " << p.m_age << endl;}

测试:

void test01()
{person<string,int> p("joyce", 21);print1(p);
}

②全局函数类外实现

首先在类内写上声明

 然后在类外实现

template<class T1,class T2>
void print2(person <T1, T2>p) // 因为有T1/T2,因此需要加上类模板
{cout << p.m_name << " " << p.m_age << endl;
}

测试:

void test02()
{person<string, int> p("joyce", 21);print2(p);
}

 依然是链接错误,因为类模板内我们写的是普通函数的声明,而下面实现写的是函数模板的实现,二者毫不相干


解决:①我们在声明的函数名后加上<>空参数列表

此时,还未完成,仍然无法使用,紧接着:

先将类外实现部分移动到类模板的最上方

 然后在类外实现部分的上面加上类模板的声明

这样下来,才能正确运行


 9、类模板案例

 实现一个通用的数组类,要求:

①可以对内置数据类型以及自定义数据类型进行存储

②将数组中的数据存储到堆区

③构造函数中要传入数组的容量

④提供拷贝构造函数以及operator=,以防止浅拷贝问题

⑤提供尾插法与尾删法对数组中数据进行增加与删除

⑥通过下标的方式访问数组中的元素

⑦获取当前数组中元素的个数以及数组的容量


首先,创建my_array类模板,包含属性p_array存储开辟的数组,m_capacity数组容量,m_size数组当前大小

template<class T>
class my_array
{
public:// 防止浅拷贝问题的拷贝构造函数与operator=my_array(int capacity);// 有参构造函数my_array(const my_array &p);// 拷贝构造函数my_array& operator=(const my_array& p); // 复制运算符的重载,返回引用才是返回自身void push_back();// 尾插void pop_back();  // 尾删int get_capacity();// 获取容量int get_size();	   // 获取大小~my_array();// 析构函数private:T* p_array;    //	存储开辟的数组int m_capacity;//	数组的容量int m_size;	   //	数组当前大小
};

然后开始实现各个函数

①有参构造函数

	my_array(int capacity)// 有参构造函数{this->m_capacity = capacity;this->m_size = 0;this->p_array = new T[this->m_capacity]; // 开辟堆区空间以存储p_arraycout << "有参构造函数" << endl;}

②拷贝构造函数

	my_array(const my_array &p)// 拷贝构造函数{this->m_capacity = p.m_capacity; this->m_size = p.m_size;//this->p_array = p.p_array;// 带来浅拷贝问题this->p_array = new T(p.m_capacity);// 根据p的大小重新开辟空间以赋值// 将p中的数据也拷贝过来for (int i = 0; i < p.m_size; i++){this->p_array[i] = p.p_array[i];}cout << "拷贝构造函数" << endl;}

③operetor=等号重载

	my_array& operator=(const my_array& p) // 赋值运算符的重载,返回引用才是返回自身{if (this->p_array != NULL) // 判断是否有属性在堆区,有则释放{delete[] this->p_array;this->p_array = NULL;this->m_capacity = 0;this->m_size = 0;}// 深拷贝this->m_capacity = p.m_capacity;this->m_size = p.m_size;this->p_array = new T[p.m_capacity]; // 重新开辟空间for (int i = 0; i < p.m_size; i++) // 数据拷贝{this->p_array[i] = p.p_array[i];}cout << "operator=" << endl;return *this;}

④析构函数

	~my_array()// 析构函数{if (this->p_array != NULL){delete[] this->p_array;this->p_array = NULL;}cout << "析构函数" << endl;}

此时,我们先运行一下各个函数

#include"my_array.hpp"
int main()
{my_array<int> arr1(5); // 有参构造my_array<int> arr2(arr1);// 拷贝构造my_array<int> arr3(100); arr1 = arr3; // 自定义类型=赋值
return0;
}


⑤尾插 数据

首先,判断数组当前元素个数size是否等于容量capacity,如果相等则数组满了,直接return。

否则,直接将传入的数据赋给数组第size个元素,arr[size],因为当前个数为size个,数组下标为0-size-1的元素都有了,下一个为空的就是第size个,因此赋给第size个

	void push_back(const T& val)// 尾插{// 先判断数组是否满if (this->m_size == this->m_capacity){return;/*		int new_capacity = m_capacity + 1;this->p_array = new T(new_capacity);*/}this->p_array[this->m_size ] = val; // 尾插数据this->m_size++;	// 当前大小+1}

⑥尾删 数据

使用户访问不到最后一个元素即可,即 使size-1,这样数组元素就少了一个

	void pop_back()  // 尾删{if (this->m_size == 0){cout << "数组为空!" << endl;return;}this->m_size--;}

⑦以下标方式访问数组元素

由于my_Array是我们自己创建的数组类型,其中的元素数据类型都是T,因此我们不能直接用[]访问到元素,需要我们重载[]运算符


我们使用[]下标访问元素是为了获取一个数据,所以我们重载就直接返回一个元素的数据就行

	T& operator[](int index){return this->p_array[index];}

返回值类型就是我们数组my_array中的数据类型T,同时保证返回前后是同一个元素我们用&


⑧获取大小与获取容量

直接返回size与capacity即可

	int get_capacity() // 获取容量{return this->m_capacity;}int get_size()	   // 获取大小{return this->m_size;}

代码完成,接下来

①我们创建数组,并打印输出

template<class T>
void print(T&arr) // 打印函数
{for (int i = 0; i < 5; i++){cout << arr[i] << " ";}
}
int main()
{my_array<int> arr1(5); // 有参构造for (int i = 0; i < 5; i++){arr1.push_back(i);}print(arr1);return 0;
}

成功输出范围内的数据

② 我们再看一下我们创建数组的容量和大小

	cout << "capacity" << arr1.get_capacity() << endl;cout << "size" << arr1.get_size() << endl;

 ③我们拷贝arr1构造arr2,并尾删数据,再打印输出

void test01()
{my_array<int> arr1(5); // 有参构造for (int i = 0; i < 5; i++){arr1.push_back(i);}print(arr1);cout << "capacity" << arr1.get_capacity() << endl;cout << "size" << arr1.get_size() << endl;my_array<int> arr2(arr1);// 拷贝构造print(arr2);arr2.pop_back();print(arr2);cout << "capacity" << arr2.get_capacity() << endl;cout << "size" << arr2.get_size() << endl;
}

 ④我们创建自定义数据类型,然后使用尾插尾删等的函数

先创建自定义数据类型person

class person
{
public:person() {};person( string name,int age ){this->m_age = age;this->m_name = name;}int m_age;string m_name;
};

创建打印函数print2(以我们自定义数据类型为准来创建)

void print2(my_array<person>& arr)
{for (int i = 0; i < arr.get_size(); i++){cout << arr[i].m_name <<arr[i].m_age<<endl;}cout << endl;
}

创建数组arr并初始化,先输出每个元素的信息与大小容量

然后尾删2个元素后,再次输出每个元素的信息与大小容量

void test02()
{// 创建数组并初始化my_array<person>arr(10);person a1("joyce", 21);person a2("tatina", 20);person a3("knaz", 40);person a4("nana", 20);person a5("yomi", 1);// 尾插到数组中arr.push_back(a1);arr.push_back(a2);arr.push_back(a3);arr.push_back(a4);arr.push_back(a5);// 打印数组中的每个元素的数据与大小容量print2(arr);cout << "capacity" << arr.get_capacity() << endl;cout << "size" << arr.get_size() << endl;// -----------------------------------------------------// 尾删2个元素arr.pop_back();arr.pop_back();// 打印数组中的每个元素的数据与大小容量print2(arr);cout << "capacity" << arr.get_capacity() << endl;cout << "size" << arr.get_size() << endl;
}


至此全部完成

相关文章:

泛型编程 之模板(template)

C另一种编程思想称为 泛型编程&#xff0c;主要利用的技术就是模板 目录 C另一种编程思想称为 泛型编程&#xff0c;主要利用的技术就是模板 一、概念 二、函数模板 1、语法与使用&#xff1a; 2、函数模板注意事项 3、普通函数与函数模板的区别 4、普通函数与函数模板的调用规…...

用ChatGPT问DotNet的相关问题,发现DotNet工程师的前景还不错

本人最近费了九牛二虎之力注册了一个ChatGPT账号&#xff0c;现在就给大家分享一下&#xff0c;问一下关于.NET的问题&#xff0c;看看ChatGPT的AI功能具体如何&#xff1f; 一、C#跟其它语言比较的优势 回答&#xff1a; C#是一门编程语言&#xff0c;它是为 Microsoft 的 …...

LeetCode_字符串_简单_415.字符串相加

目录 1.题目2.思路3.代码实现&#xff08;Java&#xff09; 1.题目 给定两个字符串形式的非负整数 num1 和num2&#xff0c;计算它们的和并同样以字符串形式返回。 你不能使用任何內建的用于处理大整数的库&#xff08;比如 BigInteger&#xff09;&#xff0c; 也不能直接将…...

Insix:面向真实的生成数据增强,用于Nuclei实例分割

文章目录 InsMix: Towards Realistic Generative Data Augmentation for Nuclei Instance Segmentation摘要本文方法数据增强方法具有形态学约束的前景增强提高鲁棒性的背景扰动 实验结果 InsMix: Towards Realistic Generative Data Augmentation for Nuclei Instance Segment…...

CleanMyMac X4.13.2最新版下载

现在cleanmymac x4.13.2中文版是大家首选的优秀mac清理软件。CleanMyMac集合了多种功能&#xff0c;几乎可以满足用户所有的清洁需求。它不仅包含各种清理功能&#xff0c;还具有卸载、维护、扩展、碎纸机等实用功能&#xff0c;可同时替代多种工具。它可以清理、优化、维护和监…...

机器学习算法原理:详细介绍各种机器学习算法的原理、优缺点和适用场景

目录 引言 二、线性回归 三、逻辑回归 四、支持向量机 五、决策树 六、随机森林 七、K-均值聚类 八、主成分分析&#xff08;PCA&#xff09; 九、K近邻算法 十、朴素贝叶斯分类器 十一、神经网络 十二、AdaBoost 十三、梯度提升树&#xff08;Gradient Boosting T…...

Spring Security 6.0系列【32】授权服务器篇之默认过滤器

有道无术,术尚可求,有术无道,止于术。 本系列Spring Boot 版本 3.0.4 本系列Spring Security 版本 6.0.2 本系列Spring Authorization Server 版本 1.0.2 源码地址:https://gitee.com/pearl-organization/study-spring-security-demo 文章目录 前言1. OAuth2Authorizati…...

.NET中比肩System.Text.Json序列化反序列化组件MessagePack

简介 官方定义&#xff1a;MessagePack是一种高效的二进制序列化格式。它允许您像JSON一样在多个语言之间交换数据。但是它更快并且更小。 MessagePack是一种开源的序列化反序列化组件&#xff0c;可支持JAVA&#xff0c;C#等主流语言。在 C# 中使用 MessagePack&#xff0c…...

Oracle删除列操作:逻辑删除和物理删除

概念 逻辑删除&#xff1a;逻辑删除并不是真正的删除&#xff0c;而是将表中列所对应的状态字段&#xff08;status&#xff09;做修改操作&#xff0c;实际上并未删除目标列数据或恢复这些列占用的磁盘空间。比如0是未删除&#xff0c;1是删除。在逻辑上数据是被删除了&#…...

找出字符串中第一个匹配项的下标、求解方程----2023/5/2

找出字符串中第一个匹配项的下标、求解方程----2023/5/2 给你两个字符串 haystack 和 needle &#xff0c;请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标&#xff08;下标从 0 开始&#xff09;。如果 needle 不是 haystack 的一部分&#xff0c;则返回 -1…...

23:宁以non-member、non-friend替换member函数

想象有个class用来表示网页浏览器。这样的class可能提供的众多函数中&#xff0c;有一些用来清除下载元素高速缓存区、清除访问过的URLs的历史记录、以及移除系统中的所有cookies&#xff1a; class WebBrowser{ public:void clearCache();void clearHistory();void removeCoo…...

Centos7安装Redis

一、安装gcc依赖 由于 redis 是用 C 语言开发&#xff0c;安装之前必先确认是否安装 gcc 环境&#xff08;gcc -v&#xff09;&#xff0c;如果没有安装&#xff0c;执行以下命令进行安装 [rootlocalhost local]# yum install -y gcc 二、下载并解压安装包 [rootlocalhost l…...

Android 项目必备(四十五)-->2023 年如何构建 Android 应用程序

Android 是什么 Android 是一种基于 Linux 内核并由 Google 开发的开源操作系统。它用于各种设备包括智能手机、平板电脑、电视和智能手表。 目前&#xff0c;Android 是世界上移动设备使用最多的操作系统; 根据 statcounter 的一份最近 12 个月的样本报告;Android 的市场份额…...

改进YOLOv5: | 涨点神器 | 即插即用| ICLR 2022!Intel提出ODConv:即插即用的动态卷积

OMNI-DIMENSIONAL DYNAMIC CONVOLUTION ODConv实验核心代码ODConv代码yaml文件运行:论文链接: https://openreview.net/forum?id=DmpCfq6Mg39 本文介绍了一篇动态卷积的工作:ODConv,其通过并行策略采用多维注意力机制沿核空间的四个维度学习互补性注意力。作为一种“即插…...

( 数组和矩阵) 485. 最大连续 1 的个数 ——【Leetcode每日一题】

❓485. 最大连续 1 的个数 难度&#xff1a;简单 给定一个二进制数组 nums &#xff0c; 计算其中最大连续 1 的个数。 示例 1&#xff1a; 输入&#xff1a;nums [1,1,0,1,1,1] 输出&#xff1a;3 解释&#xff1a;开头的两位和最后的三位都是连续 1 &#xff0c;所以最大…...

从0搭建Vue3组件库(十一): 集成项目的编程规范工具链(ESlint+Prettier+Stylelint)

欲先善其事,必先利其器。一个好的项目是必须要有一个统一的规范,比如代码规范,样式规范以及代码提交规范等。统一的代码规范旨在增强团队开发协作、提高代码质量和打造开发基石,所以每个人必须严格遵守。 本篇文章将引入 ESLintPrettierStylelint 来对代码规范化。 ESlint ES…...

Mysql 苞米豆 多数据源 读写分离(小项目可用)

目录 0 课程视频 1 配置 1.1 加依赖 1.2 yml 配置文件 -> druid配置后报错 搞不定 2 代码 2.1 实体类 2.2 mapper -> 调用操作数据库方法 操作数据库 2.3 service -> 指定数据源 -> 用Mapper 接口 -> 操作数据库 2.4 controller -> 用户使用接口 -&…...

OJ练习第90题——删除字符使频率相同

删除字符使频率相同 力扣链接&#xff1a;2423. 删除字符使频率相同 题目描述 给你一个下标从 0 开始的字符串 word &#xff0c;字符串只包含小写英文字母。你需要选择 一个 下标并 删除 下标处的字符&#xff0c;使得 word 中剩余每个字母出现 频率 相同。 如果删除一个字…...

云原生Istio基本介绍

目录 1 什么是Istio2 Istio特征2.1 连接2.2 安全2.3 策略2.4 观察 3 Istio与服务治理3.1服务治理的三种形态 4 Istio与Kubernetes4.1 Kubernetes介绍4.2 Istio是Kubernetes的好帮手4.3 Kubernetes是Istio的好基座 5 Istio与服务网格5.1 时代选择服务网格5.2 服务网格选择Istio …...

Vue(简单了解Cookie、生命周期)

一、了解Cookie 类似于对象响应携带数据 输入用户名密码跳转到指定页面 点击指定页面中其中一个按钮跳转到另一个指定页面&#xff08;再不需用输入用户名密码&#xff09; 例如现在很多浏览器实现七天免密登录 简单理解&#xff1a;就是在网站登录页面之后&#xff0c;服务…...

idea大量爆红问题解决

问题描述 在学习和工作中&#xff0c;idea是程序员不可缺少的一个工具&#xff0c;但是突然在有些时候就会出现大量爆红的问题&#xff0c;发现无法跳转&#xff0c;无论是关机重启或者是替换root都无法解决 就是如上所展示的问题&#xff0c;但是程序依然可以启动。 问题解决…...

Leetcode 3576. Transform Array to All Equal Elements

Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接&#xff1a;3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到&#xf…...

Unity3D中Gfx.WaitForPresent优化方案

前言 在Unity中&#xff0c;Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染&#xff08;即CPU被阻塞&#xff09;&#xff0c;这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案&#xff1a; 对惹&#xff0c;这里有一个游戏开发交流小组&…...

大语言模型如何处理长文本?常用文本分割技术详解

为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...

macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用

文章目录 问题现象问题原因解决办法 问题现象 macOS启动台&#xff08;Launchpad&#xff09;多出来了&#xff1a;Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显&#xff0c;都是Google家的办公全家桶。这些应用并不是通过独立安装的…...

关键领域软件测试的突围之路:如何破解安全与效率的平衡难题

在数字化浪潮席卷全球的今天&#xff0c;软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件&#xff0c;这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下&#xff0c;实现高效测试与快速迭代&#xff1f;这一命题正考验着…...

保姆级教程:在无网络无显卡的Windows电脑的vscode本地部署deepseek

文章目录 1 前言2 部署流程2.1 准备工作2.2 Ollama2.2.1 使用有网络的电脑下载Ollama2.2.2 安装Ollama&#xff08;有网络的电脑&#xff09;2.2.3 安装Ollama&#xff08;无网络的电脑&#xff09;2.2.4 安装验证2.2.5 修改大模型安装位置2.2.6 下载Deepseek模型 2.3 将deepse…...

CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝

目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为&#xff1a;一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...

解读《网络安全法》最新修订,把握网络安全新趋势

《网络安全法》自2017年施行以来&#xff0c;在维护网络空间安全方面发挥了重要作用。但随着网络环境的日益复杂&#xff0c;网络攻击、数据泄露等事件频发&#xff0c;现行法律已难以完全适应新的风险挑战。 2025年3月28日&#xff0c;国家网信办会同相关部门起草了《网络安全…...

毫米波雷达基础理论(3D+4D)

3D、4D毫米波雷达基础知识及厂商选型 PreView : https://mp.weixin.qq.com/s/bQkju4r6med7I3TBGJI_bQ 1. FMCW毫米波雷达基础知识 主要参考博文&#xff1a; 一文入门汽车毫米波雷达基本原理 &#xff1a;https://mp.weixin.qq.com/s/_EN7A5lKcz2Eh8dLnjE19w 毫米波雷达基础…...