个人技术分享

🎩 欢迎来到技术探索的奇幻世界👨‍💻

📜 个人主页@一伦明悦-CSDN博客

✍🏻 作者简介: C++软件开发、Python机器学习爱好者

🗣️ 互动与支持💬评论      👍🏻点赞      📂收藏     👀关注+

如果文章有所帮助,欢迎留下您宝贵的评论,

点赞加收藏支持我,点击关注,一起进步!

目录

前言             

正文

01-模板简介             

02-函数模板语法             

03-函数模板注意事项             

04-函数模板案例             

05-普通函数与函数模板的区别             

06-普通函数和函数模板调用规则            

总结      


前言             

        在C++中,泛型编程是一种编程范式,其核心思想是编写与数据类型无关的通用代码,以实现对不同数据类型的操作和算法。它主要利用的技术是模板。

        泛型编程的优点:

                代码复用: 泛型编程使得代码能够被多次使用,不需要为每种数据类型单独编写相似的代码。

                灵活性: 泛型编程可以适应不同类型的数据,使得代码更加灵活和通用。

                类型安全: 使用模板的泛型编程可以在编译期间进行类型检查,提高代码的安全性和可靠性。

正文

01-模板简介             

        模板: 模板是C++中的一种特殊机制,允许程序员编写通用的代码模板,而不是针对特定的数据类型编写代码。通过模板,可以定义类或函数,使其可以接受任意数据类型作为参数,从而实现代码的通用性和复用性        

        模板的使用方式:

                函数模板: 允许定义函数,其参数类型可以是任意类型。例如,可以编写一个泛型的排序函数,用于对不同类型的数组进行排序。

                类模板: 允许定义类,其成员或方法可以使用任意类型作为参数。例如,可以定义一个泛型的容器类,用于存储任意类型的数据。

        代码示例如下:通过泛型编程和模板技术,可以使C++代码更具灵活性和通用性,提高代码的重用性和可维护性。

// 函数模板示例:泛型的排序函数
template <typename T>
void bubbleSort(T arr[], int n) {
    for (int i = 0; i < n - 1; ++i) {
        for (int j = 0; j < n - i - 1; ++j) {
            if (arr[j] > arr[j + 1]) {
                T temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}

// 类模板示例:泛型的容器类
template <typename T>
class Container {
private:
    T data;
public:
    Container(T d) : data(d) {}
    T getData() { return data; }
};

int main() {
    // 使用函数模板
    int intArr[] = {3, 1, 4, 1, 5, 9, 2, 6};
    bubbleSort(intArr, 8);

    // 使用类模板
    Container<int> intContainer(42);
    std::cout << "Data in intContainer: " << intContainer.getData() << std::endl;

    return 0;
}

02-函数模板语法             

        函数模板是一种通用的函数定义方式,允许编写可以处理多种数据类型的函数。函数模板使用模板参数来表示数据类型,从而实现函数的通用性和复用性。 

        函数模板的语法如下:其中,template <typename T> 表示声明一个模板,typename T 是模板参数,可以是任意类型。在函数定义中,可以使用这个模板参数 T 来表示函数的参数类型或返回类型。

template <typename T>
返回类型 函数名(参数列表) {
    // 函数体
}

        下面是一个函数模板的具体示例和解释:这样,函数模板使得可以编写一次代码,就可以用于处理多种数据类型,提高了代码的灵活性和复用性。

        在示例中,getMax 是一个函数模板,它使用了模板参数 T 来表示函数参数的类型。

        在 main 函数中,通过 getMax<int>(3, 7) 调用函数模板,并指定 T 的类型为 int,这样编译器会生成一个处理整数的版本。

        同样地,通过 getMax<double>(3.14, 2.718) 调用函数模板,并指定 T 的类型为 double,这样编译器会生成一个处理浮点数的版本。

        函数模板根据实际调用时的参数类型来实例化生成对应的函数代码,从而实现对不同数据类型的通用操作。

#include <iostream>

// 函数模板示例:获取两个数的最大值
template <typename T>
T getMax(T a, T b) {
    return (a > b) ? a : b;
}

int main() {
    // 使用函数模板获取两个整数的最大值
    int maxInt = getMax<int>(3, 7);
    std::cout << "Max integer: " << maxInt << std::endl;

    // 使用函数模板获取两个浮点数的最大值
    double maxDouble = getMax<double>(3.14, 2.718);
    std::cout << "Max double: " << maxDouble << std::endl;

    return 0;
}

        下面给出具体代码分析函数模板的应用过程:这段代码演示了使用函数模板来实现通用的数据交换功能。在传统的方法中,针对不同的数据类型(如整型和浮点型),需要编写不同的交换函数(swap01swap02),这样会导致代码重复繁琐。

        而通过函数模板,可以实现一个通用的交换函数 swap03,该函数可以处理任意类型的数据交换。在 test01 函数中,展示了如何使用函数模板进行数据交换,并且可以自动推导类型或者显式指定类型。这样大大简化了代码,并提高了代码的可维护性和扩展性。最后,程序中使用了 sizeof(string) 来获取 string 类型的大小

#include <iostream>
using namespace std;
#include <string>
/*  C++另一种编程思想称为 泛型编程 ,主要利用的技术就是模板
    C++提供两种模板机制:函数模板和类模板
	建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,用一个虚拟的类型来代表。

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


	template --- 声明创建模板
	typename --- 表面其后面的符号是一种数据类型,可以用class代替
	T --- 通用的数据类型,名称可以替换,通常为大写字母

*/

// 两个整型交换
void swap01(int &a, int &b)
{
	int temp = a;
	a = b;
	b = temp;

}

// 两个浮点型交换
void swap02(double &a, double &b)
{
	double temp = a;
	a = b;
	b = temp;

}


// 也就说每一种数据类型的更换都要写一个函数实现,过于繁琐,因此可以使用模板

// 声明使用函数模板
template<typename T>  // 声明,告诉编译器不用报错  typename可以用class代替,后面就直接写class

void swap03(T &a, T &b)
{
	T temp = a;
	a = b;
	b = temp;
}

void test01()
{
	int a = 10;
	int b = 20;

//	swap01(a, b);

	// 函数模板的使用
	// 1、编译器自动推导
	swap03(a, b);

	// 2、显示指定类型
//	swap03<int>(a, b);

	cout << "a= " << a << endl;
	cout << "b= " << b << endl;

	double c = 2.2;
	double d = 1.1;

//	swap02(c, d);
	cout << "c= " << c << endl;
	cout << "d= " << d << endl;

}

// 也就说每一种数据类型的更换都要写一个函数实现,过于繁琐,因此可以使用模板


int main()
{
	test01();

	cout << "char = " << sizeof(string) << endl;

	system("pause");
	return 0;
}

        示例运行结果如下图所示:

03-函数模板注意事项             

        函数模板的使用需要注意以下几点:

        模板参数的命名: 模板参数的命名可以是任意合法的标识符,但通常建议使用有意义的名称,以提高代码的可读性和可维护性。

        模板参数的限制: 模板参数可以是任意类型,但有时可能需要对模板参数进行限制,以确保模板能够在所有情况下正确工作。可以使用类型约束或者static_assert来实现参数的限制。

        模板参数的默认值: 可以为模板参数指定默认值,使得在实际使用时可以省略模板参数的指定。这样可以提高模板的灵活性,并减少代码的冗余性。

        模板的具体化和特化: 可以对模板进行具体化或特化,以适应特定的数据类型或情况。具体化是指为特定类型提供特定版本的模板实现,而特化是指为特定类型提供特定的模板参数。

        模板函数与重载函数的区别: 当模板函数与重载函数同时存在时,编译器会优先选择重载函数而不是模板函数。因此,需要注意避免模板函数与重载函数产生二义性。

        下面是一个具体的示例代码,演示了函数模板的注意事项:

        在 func 函数模板中,可以自动推导参数类型或者显式指定模板参数类型。

        当函数模板与重载函数同时存在时,编译器会优先选择重载函数,因此需要注意避免模板函数与重载函数产生二义性。

#include <iostream>

// 函数模板示例:获取两个数的最大值
template <typename T>
T getMax(T a, T b) {
    return (a > b) ? a : b;
}

// 函数模板注意事项示例
template <typename T>
void func(T x) {
    std::cout << "Generic function called with argument: " << x << std::endl;
}

// 函数模板重载函数的注意事项示例
template <typename T>
void func(T x, T y) {
    std::cout << "Overloaded function called with arguments: " << x << " and " << y << std::endl;
}

int main() {
    // 使用函数模板获取两个整数的最大值
    int maxInt = getMax<int>(3, 7);
    std::cout << "Max integer: " << maxInt << std::endl;

    // 函数模板注意事项示例
    func(5); // 编译器可以推导出参数类型
    func<int>(5); // 显式指定模板参数类型

    // 函数模板重载函数的注意事项示例
    func(1, 2); // 调用重载函数,而不是模板函数

    return 0;
}

04-函数模板案例             

   下面给出一个模板案例分析应用过程,
   1、利用函数模板封装一个排序的函数,可以对不同数据类型数组进行排序
   2、排序规则从大到小,排序算法为选择排序
   3、分别利用char数组和int数组进行测试

        代码解释如下:

        函数模板 mySwap 用于交换两个值。它的模板参数 T 表示任意类型。通过引用传递参数 a 和 b,实现了在函数内部直接修改传入的参数值。

        函数模板 mySort 用于对数组进行排序。它的模板参数 T 表示数组元素的类型。通过选择排序的方法,对传入的数组 arr 进行排序。

        函数模板 printArray 用于打印数组。它的模板参数 T 表示数组元素的类型。通过遍历数组并逐个打印元素,实现了对数组的打印功能。

        函数 test01 测试了 mySort 和 printArray 函数模板对 char 数组的排序和打印。首先创建了一个 char 数组 charArr,然后计算数组的长度,并调用 mySort 对数组进行排序,最后调用 printArray 打印排序后的数组。

        函数 test02 测试了 mySort 和 printArray 函数模板对 int 数组的排序和打印。首先创建了一个 int 数组 intArr,然后计算数组的长度,并调用 mySort 对数组进行排序,最后调用 printArray 打印排序后的数组。

        在 main 函数中,依次调用了 test01 和 test02 函数来进行测试,并使用 system("pause") 来暂停程序的执行,以便查看输出结果。

#include <iostream>
using namespace std;


// 交换的函数模板
template<class T>
void mySwap(T &a, T &b)
{
	T temp = a;
	a = b;
	b = temp;
}
// 排序算法
template <class T>
void mySort(T arr[],int len)   // 因为是对字符串和int类型数组,因此定义一个T类型数组
{
	for (int i = 0; i < len;i++)
	{
		int max = i;  // 认定最大值的下标
		for (int j = i + 1; j < len;j++)
		{
			if (arr[max]<arr[j])
			{
				max = j;
			}
		}
		if (max!=i)
		{
			// 交换元素  ,也可以使用模板
			mySwap(arr[max], arr[i]);
		}
	}

}
// 打印数组模板
template<class T>
void printArray(T arr[],int len)
{
	for (int i = 0; i < len;i++)
	{
		cout << arr[i] << " ";
	}
	cout << endl;
}

void test01()
{
	// 测试char数组
	char charArr[] = "abcfed";
	int num = sizeof(charArr) / sizeof(char);
	mySort(charArr, num);
	printArray(charArr, num);
}

void test02()
{
	// 测试int数组
	int intArr[] = { 2,3,4,1,5,7 };
	int num = sizeof(intArr) / sizeof(intArr[0]);  // 也可以直接传入数据类型相除
	mySort(intArr, num);
	printArray(intArr, num);
}



int main()
{
	test01();
	test02();


	system("pause");
	return 0;
}

        案例运行结果如下图所示: 

05-普通函数与函数模板的区别             

        普通函数和函数模板在C++中有一些重要区别:

        普通函数(Non-Template Function):

        固定数据类型: 普通函数的参数和返回值的数据类型是固定的,不能灵活适应不同数据类型的需求。

        单一实现: 普通函数只能有一份具体的实现,不能直接用于多种数据类型。

        类型安全: 普通函数在参数传递和返回值时,会进行隐式的类型转换,可能带来类型不匹配的问题。

        代码冗余: 需要为不同的数据类型编写多个相似但不同的函数,容易造成代码冗余。

        函数模板(Function Template):

        通用性: 函数模板允许定义参数化类型,实现代码的通用性,可以根据具体需求来生成适合不同数据类型的函数。

        灵活性: 函数模板可以适应不同数据类型,根据要处理的数据类型自动生成相应的函数实例。

        代码重用: 函数模板可以减少重复编写代码的工作,提高代码的重用性。

        类型安全: 函数模板提供了更严格的类型检查,避免了隐式类型转换带来的问题。

        下面是一个具体的示例代码,演示了普通函数和函数模板的区别:在这个示例中,我们定义了一个普通函数 getMax 和一个函数模板 getMaxTemplate,它们都用于求两个数的最大值。通过比较这两种方式,可以看出函数模板具有更高的通用性和灵活性,能够适应不同数据类型的需求。

#include <iostream>
using namespace std;

// 普通函数:求两个整数的最大值
int getMax(int a, int b) {
    return (a > b) ? a : b;
}

// 函数模板:求两个数的最大值
template <typename T>
T getMaxTemplate(T a, T b) {
    return (a > b) ? a : b;
}

int main() {
    // 普通函数示例
    int max1 = getMax(3, 7);
    cout << "Max integer (non-template): " << max1 << endl;

    // 函数模板示例
    int max2 = getMaxTemplate<int>(3, 7); // 指定模板参数为int
    cout << "Max integer (template): " << max2 << endl;

    double max3 = getMaxTemplate<double>(3.5, 7.8); // 指定模板参数为double
    cout << "Max double (template): " << max3 << endl;

    return 0;
}

        下面给出具体代码分析应用过程:

/*  1、普通函数调用时可以发生自动类型转换(隐式类型转换)
    2、函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
    3、如果利用显示指定类型的方式,可以发生隐式类型转换
*/

        这段代码展示了隐式类型转换的情况。解释如下:

        定义了一个普通函数 Add,接受两个 int 类型的参数并返回它们的和。

        在 test01 函数中,定义了一个整数变量 a 和一个字符变量 ch,然后调用 Add 函数传入 a 和 ch

        因为 Add 函数参数类型是 int,而传入的参数 ch 是字符型,发生了隐式类型转换,字符 'c' 在ASCII码中对应的整数值是 99,所以最终输出的结果是 a + 99,即 10 + 99,结果为 109。

        这段代码演示了当函数参数类型与传入参数类型不匹配时,C++会进行隐式类型转换,将传入参数转换为函数参数所需的类型,然后执行函数。

#include<iostream>
using namespace std;



// 隐式类型转换
int Add(int a, int b)
{
	return a + b;
}

void test01()
{
	int a = 10;
	int b = 20;
	char ch = 'c';
	cout << Add(a, ch) << endl;  // 传入ch时发生隐式类型转换,字符c等于99
}
int main()
{
	test01();


	system("pause");
	return 0;
}

        示例运行结果如下图所示:

06-普通函数和函数模板调用规则            

        普通函数和函数模板在调用时有一些不同之处,解释如下:

        普通函数调用规则:

        参数匹配: 普通函数调用时,编译器会进行参数匹配,确保传入的参数类型和数量与函数声明或定义中的参数类型和数量匹配。

        重载解析: 如果有多个同名函数,编译器会根据传入参数的类型和数量来选择最合适的函数进行调用。

        隐式类型转换: 如果传入的参数类型与函数参数类型不匹配,但可以通过隐式类型转换实现匹配,编译器会进行隐式类型转换并调用相应的函数。

        具体函数实现: 最终调用的是与传入参数类型最匹配的具体函数实现。

        函数模板调用规则:

        模板参数推断: 当调用函数模板时,编译器会根据传入的参数类型自动推断模板参数的类型。

        具体函数生成: 根据推断出的模板参数类型,编译器会生成相应的函数实例。

        类型匹配: 生成的函数实例与传入参数类型匹配,实现了模板参数的具体化。

        重载解析: 如果存在多个模板实例与传入参数类型匹配,编译器会根据重载解析规则选择最合适的函数实例进行调用。

 调用规则
/* 1. 如果函数模板和普通函数都可以实现,优先调用普通函数
   2. 可以通过空模板参数列表来强制调用函数模板
   3. 函数模板也可以发生重载
   4. 如果函数模板可以产生更好的匹配,优先调用函数模板
*/

        下面是一个具体的代码示例,演示了普通函数和函数模板的调用规则:在这个示例中,我们调用了一个普通函数 Add 和一个函数模板 AddTemplate。通过比较它们的调用过程,可以清楚地看出普通函数的参数匹配和重载解析,以及函数模板的模板参数推断和具体函数生成。

#include <iostream>
using namespace std;

// 普通函数:求两个整数的和
int Add(int a, int b) {
    cout << "Calling non-template function" << endl;
    return a + b;
}

// 函数模板:求两个数的和
template <typename T>
T AddTemplate(T a, T b) {
    cout << "Calling template function" << endl;
    return a + b;
}

int main() {
    // 普通函数示例
    int sum1 = Add(3, 7);
    cout << "Sum (non-template): " << sum1 << endl;

    // 函数模板示例
    int sum2 = AddTemplate(3, 7); // 推断模板参数为int
    cout << "Sum (template): " << sum2 << endl;

    double sum3 = AddTemplate(3.5, 7.8); // 推断模板参数为double
    cout << "Sum (template): " << sum3 << endl;

    return 0;
}

总结      

        函数模板是C++中一种强大的工具,它提供了编写通用代码的能力,使得可以处理多种数据类型而无需重复编写函数的代码。以下是函数模板的主要特点和总结:

        通用性: 函数模板允许我们定义函数,这些函数可以处理多种数据类型,而不需要为每种数据类型编写单独的函数。

        模板参数: 函数模板使用模板参数来代表类型或值,这些参数在函数体中被使用,允许函数模板适应不同的输入数据类型。

        模板参数推断: 当调用函数模板时,通常不需要显式指定模板参数,编译器可以根据传入的实参推断出模板参数的类型。

        代码重用和减少冗余: 函数模板可以减少代码的重复编写,提高代码的重用性,避免因为不同数据类型而导致的代码冗余。

        类型安全: 函数模板提供了严格的类型检查,避免了隐式类型转换带来的问题,使得代码更加健壮。

        生成具体函数实例: 模板参数推断后,编译器会根据推断出的具体类型生成相应的函数实例,这些实例是编译时确定的。

        重载解析: 如果有多个模板实例与传入参数类型匹配,编译器会根据重载解析规则选择最合适的函数实例进行调用。

        编译时生成: 函数模板是在编译时进行实例化的,而不是在运行时,因此不会带来额外的性能开销。

        总结来说,函数模板是C++中用于提高代码通用性和可重用性的重要工具,它使得我们可以以一种更灵活的方式处理不同类型的数据,同时保证了类型安全和效率。